Skip to content

Why Viper?

Here’s everything I love about Viper and why I think you should give it a shot.

Viper itself has a tiny api surface area, acting only as the orchestrator.

Your application is a standard laravel app that serves both the backend and frontend.

Instead of reinventing the wheel Viper automatically generates code that glues existing technologies you already know and love to provide a powerful developer experience.

  • Backend - laravel
  • Frontend - vue
  • Client side routing - vue-router
  • Queries - @tanstack/vue-query
  • Mutations - @tanstack/vue-query
  • TypeScript generators - spatie/typescript-transformer

File based routes are registered to be served by both Laravel and Vue Router

  • resources/js/pages/users/[user]/edit.vue
    • GET users/{user}/edit (laravel)
    • GET users/:user/edit (vue router)

Supports route grouping via directories without affecting the url

  • (app)/home.vue => GET /home

Provide layouts to all subroutes, nest infinitely

  • _layout.vue
  • home.vue
  • Directoryusers
    • _layout.vue
    • index.vue
    • Directory[user]
      • _layout.vue
      • edit.vue
      • index.vue

_layout.vue files always render a standard <router-view></router-view>.

Choose to either prefetch your routes so the data is ready when the page mounts, or fetch after navigating for whichever experience you prefer.

Provide fully typed props and actions to your vue components by writing php inside them

<template>
<!-- fully typed as user.name: string -->
<p>{{ user.name }}</p>
</template>
<script setup lang="ts">
import { usePage } from '@ozmos/viper-vue';
// type from the php file automatically generated and available in the file
const page = usePage<ViperGen.HomePage>();
// @tanstack/vue-query to access the prop (strongly typed!)
const { data: user } = page.useQuery('user');
</script>
<php>
use \App\Dto\UserDto;
return new class {
#[\Ozmos\Viper\Attrs\Prop]
public function user(): UserDto
{
return UserDto::from(request()->user());
}
};
</php>

If you just violently recoiled at the mere thought of writing php inside your vue files you can place an adjacent php file next to the vue file instead and it will work exactly the same

resources/js/pages/home.vue
<template>
<!-- fully typed as user.name: string -->
<p>{{ user.name }}</p>
</template>
<script setup lang="ts">
import { usePage } from '@ozmos/viper-vue';
const page = usePage<ViperGen.HomePage>();
const { data: user } = page.useQuery('user');
</script>
resources/js/pages/home.php
<?php
use \App\Dto\UserDto;
return new class {
#[\Ozmos\Viper\Attrs\Prop]
public function user(): UserDto
{
return UserDto::from(request()->user());
}
};

You can also have Viper turn your SFC code into adjacent code and vice versa if you decide to switch later on

Terminal window
php artisan viper:invert

Use middleware, route names, and the service container

resources/js/pages/users/[User].vue
<template>
<p>{{ user.name }}</p>
</template>
<script setup lang="ts">
import { usePage } from '@ozmos/viper-vue';
const page = usePage<ViperGen.UsersUserParam>();
const { data: user } = page.useQuery('user');
</script>
<php>
use \App\Dto\UserDto;
use \App\Models\User;
use \Ozmos\Viper\Attrs;
return new
#[Attrs\Name("users.show")]
#[Attrs\Middleware(["auth", "can:view,user"])]
class {
#[Attrs\Prop]
public function user(User $user): UserDto
{
return UserDto::from($user);
}
};
</php>

Automatically pass api endpoints to your pages ready to be consumed

<template>
<!-- You can also pass fully-typed (from the DTO) overrides: form.mutate({ name: "something else" }) -->
<form method="POST" @submit.prevent="form.mutate()">
<input v-model="form.state.value.name" />
<p v-if="form.errors.value.name">{{ form.errors.value.name }}</p>
<button type="submit">Submit</button>
</form>
<button @click="perform.mutate()">Do something else</button>
</template>
<script setup lang="ts">
import { usePage } from '@ozmos/viper-vue';
// type from the php file automatically generated and available in the file
const page = usePage<ViperGen.HomePage>();
// @tanstack/vue-query to access the action with a nice form helper
const form = page.useForm('createUser', {
state: {
name: "",
},
onSuccess(data) {
alert(data.name);
},
});
// or just the useMutation without the form state/form error parsing
const perform = page.useMutation('performSomeAction', {
onSuccess() {
// ...
}
});
</script>
<php>
use \App\Dto\UserDto;
use \App\Dto\CreateUserDto;
return new class {
#[\Ozmos\Viper\Attrs\Action]
public function createUser(CreateUserDto $data): UserDto
{
return UserDto::from(User::create($data));
}
#[\Ozmos\Viper\Attrs\Action]
public function performSomeAction()
{
// do something else...
}
};
</php>