DEV Community

Martin Pham
Martin Pham

Posted on

Fully type-safe integration between your Laravel backend and your frontend

TLDR: If you've ever wanted fully type-safe integration between your Laravel backend and your frontend (whether that's API consumers or Inertia.js apps), the laravel-type-generator package might save you a ton of time. I just released this package that I've been working on for a while.

Why I Built It

I wanted a completely type-safe workflow between Laravel and the frontend — so backend changes wouldn't silently break my frontend.

The Idea

The concept is simple: use OpenAPI to describe your API documentation, then feed it to tools like Orval to automatically generate TypeScript clients and types.

I tried several existing packages, but none gave me the accuracy or automation I was looking for. So I decided to build my own.

What It Does

The laravel-type-generator package scans your Laravel routes, controllers, models, DTOs, and their properties, methods, docblocks, and PHP source code. It then automatically generates:

  • TypeScript types from your Laravel routes
  • OpenAPI specifications for API documentation
  • Swagger UI to visualize and interact with your OpenAPI spec
  • Inertia.js type generation
  • Accurate handling of pagination, collections, and complex return types

(More transformers are coming soon!)

Think of it as a bridge that keeps your Laravel backend and frontend perfectly synchronized with types.

Installation

composer require martinpham/laravel-type-generator
Enter fullscreen mode Exit fullscreen mode

Publish the config:

php artisan vendor:publish --tag="type-generator"
Enter fullscreen mode Exit fullscreen mode

Then run:

php artisan types:generate
Enter fullscreen mode Exit fullscreen mode

Configuration

The package is flexible. In the config file, you can customize what gets generated and where it goes. Here's a practical example showing how to generate:

  • An OpenAPI specification JSON file for routes matching /_api/*
  • TypeScript types for Inertia.js routes and template variables, for controllers matching App\Http\Controllers\Web\*
'route_prefixes' => [
    'uri:_api' => [
        'output' => resource_path('api/openapi.json'),
        'class' => 'MartinPham\TypeGenerator\Writers\OpenAPI\OpenAPI',
        'options' => [
            'openapi' => '3.0.2',
            'title' => 'OpenAPI',
            'version' => '1.0.0'
        ]
    ],
    'controller:App\Http\Controllers\Web\\' => [
        'output' => resource_path('js/types/inertia-routes.ts'),
        'class' => 'MartinPham\TypeGenerator\Writers\Inertia\Inertia',
    ],
],
Enter fullscreen mode Exit fullscreen mode

Example: How Your Controller Becomes Typed API Specs

Let's say you have a simple controller method like this:

class PlaygroundApiController extends Controller
{
    public function findUser(User $user): User
    {
        return $user;
    }
}
Enter fullscreen mode Exit fullscreen mode

Here's what happens under the hood:

  1. The package analyzes the findUser method's input parameter (User $user) and return type (User).
  2. It automatically generates an OpenAPI operation for this route with $user as a parameter.
  3. It creates a User schema for both request and response types.
  4. Since your User model has relationships (like Address and Country), it recursively generates schemas for those related models too — so your OpenAPI spec and TypeScript types reflect the complete data structure.

This means your frontend gets fully typed models, including nested relationships, without any extra manual work!

OpenAPI

Seamless Integration with Orval for Type-Safe API Clients & Inertia template

Once laravel-type-generator creates your OpenAPI spec, you can feed it directly into Orval to automatically generate TypeScript types and API clients.

Here's what Orval would produce from the generated spec:

export interface User {
  id?: number;
  name?: string;
  email?: string;
  role?: string;
  address?: Address;
}

export interface Address {
  id?: number;
  street?: string;
  country?: Country;
}

export interface Country {
  id?: number;
  name?: string;
  code?: string;
}

export const apiPlaygroundUsers = (
  user: string, 
  options?: AxiosRequestConfig
): Promise<AxiosResponse<User>> => {
  return axios.get(`/_api/playground/findUser/${user}`, options);
};
Enter fullscreen mode Exit fullscreen mode

What this means for you:

  • Your frontend knows exactly what data to expect — including nested objects like Address and Country
  • Your API calls are fully typed, with correct parameter and response types
  • No more guesswork or brittle any types when consuming your Laravel backend APIs

This creates a tight feedback loop between your backend and frontend, boosting developer confidence and reducing bugs from mismatched data structures.

Inertia template

On Inertia side, you will receive exactly the types which were returned by the route

Inertia

Full Support for Various Data Types

The laravel-type-generator isn't limited to just Eloquent models. It also supports:

  • Plain PHP classes
  • Laravel Data objects (like from Spatie's laravel-data package)
  • API Resources
  • Your own custom-defined docblocks

To give you more control over the generated OpenAPI spec, you can use special docblock annotations in your controller methods:

  • @id — Overrides the default operationId (otherwise it's derived from the route name)
  • @tag — Adds tags to group and organize your API operations
  • @throws — Specify errors that could be thrown during the call
  • Method descriptions are also captured in the OpenAPI document

This flexibility lets you define complex return types with complete accuracy. For example:

/**
 * @return Collection<int, User>
 */
public function allUsers(User $user): Collection
{
    return User::all();
}

/**
 *
 * @return array{
 *     requestedAt: DateTime | null,
 *     users: User[],
 *     cars: Collection<int, Car>,
 *     carsWithPagination: LengthAwarePaginator<int, Car>,
 *     nestedObject: array{
 *         evenDeeper: array{
 *             message: string
 *         }
 *     }
 * }
 */
public function manything(Address $address): array
{
    return [];
}
Enter fullscreen mode Exit fullscreen mode

Here's what's happening:

The package reads the docblocks and uses PHP's native type hints to generate detailed, nested TypeScript types and OpenAPI schemas.

Collections, paginated results, arrays with nested objects — everything gets parsed and converted correctly.

This means you can describe even very complex API responses in your controller methods and get fully typed clients on the frontend without any manual synchronization.

Specifying Request Parameters

Your API request parameters can come from different places, and laravel-type-generator supports all of them to generate accurate types:

1. Route Path Parameters

Simply define parameters in your route URL, like /users/{user}/. These will be automatically detected and typed.

2. FormRequest Subclasses

You can define a FormRequest class with typed properties and validation rules. The package inspects both your docblock properties and validation rules to generate the parameter types.

Example:

/**
 * @property UploadedFile $file
 * @property string $nickname
 * @property array{
 *     name: string,
 *     age: int
 * } $person
 */
class GreetingRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'file' => ['file'],
        ];
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Docblock Annotations on Controller Methods

You can also specify request shapes directly in your controller method's docblocks using generics and param tags:

/**
 * Greet
 *
 * Generates a greeting message.
 *
 * @id greeting
 * @param Request<array{
 *     user_id: int
 * }> $request
 * @param string $name The receiver's nickname.
 * @return Message|null The greeting message
 * @throws InvalidName Invalid name provided
 * @throws \InvalidArgumentException Invalid args provided
 */
public function greeting(Request $request, $name): ?Message
{
    // ...
}
Enter fullscreen mode Exit fullscreen mode

This tells the package exactly what your request parameters should look like, allowing for precise type generation.

Development & Feedback

This package is under very active development, and I’m constantly working to improve it.

I hope you find it useful! Feel free to try it out, and please suggest any features or improvements you’d like to see.

Your feedback helps make this tool better for everyone!

Top comments (0)