Rafhael Marsigli Logo

Garanta 100% de tipagem segura do Laravel ao React

5 min de leitura
Garanta 100% de tipagem segura do Laravel ao React

Se você acompanhou meus posts recentes, sabe que estou em uma cruzada para eliminar “mágica” das minhas aplicações Laravel. Troquei arrays associativos frouxos por DTOs estritos e substituí Observers implícitos por Services explícitos. Meu backend agora é uma verdadeira fortaleza de estabilidade.

Mas, recentemente, percebi que ainda havia um enorme buraco na minha armadura.

Eu passava horas criando objetos UserData perfeitamente tipados em PHP. Então eu os enviava para o Inertia.js e… poof. Assim que esses dados atravessavam a ponte para o React (ou Vue), viravam o Velho Oeste. Meus componentes de frontend tratavam as props como any ou, pior ainda, eu acabava escrevendo manualmente interfaces TypeScript que estavam sempre fora de sincronia com o backend.

Foi assim que eu corrigi a última brecha da stack usando o Laravel Data da Spatie combinado com o Spatie TypeScript Transformer.

Sincronização manual é um pesadelo

Vamos ser sinceros: manter os tipos do frontend em sincronia com os modelos do backend é uma batalha perdida.

Você muda uma propriedade em PHP de is_active para status. Atualiza o banco de dados. Atualiza o DTO. Mas esquece de atualizar a interface UserProps no arquivo types.ts do frontend.

O compilador não reclama. O build passa. Você faz o deploy. Então, um usuário clica em um botão e a tela fica branca. Uncaught TypeError: Cannot read properties of undefined.

Aceitamos essa fragilidade como “parte do trabalho”. Não precisa ser assim.

Automatizando o contrato

A solução é deixar o próprio código PHP escrever o código TypeScript. Como já estamos usando o spatie/laravel-data para definir nossas estruturas de dados, podemos usar o pacote spatie/laravel-typescript-transformer para gerar automaticamente as definições em TS sempre que nossas classes mudarem.

Isso não é apenas um “nice to have”. É a diferença entre esperar que o frontend funcione e saber que ele funciona.

O “not-magic” em ação

Veja como funciona a configuração. Você instala o pacote e, de repente, seus DTOs se tornam a única fonte de verdade de toda a aplicação, de ponta a ponta.

Vamos olhar para nossa confiável classe UserData do post anterior. Com uma configuração simples, podemos instruir o Laravel a transformar essa classe em uma interface TypeScript automaticamente.

namespace App\Data;

use Spatie\LaravelData\Data;
use Spatie\TypeScriptTransformer\Attributes\TypeScript;

#[TypeScript]
class UserData extends Data
{
    public function __construct(
        public int $id,
        public string $name,
        public string $email,
        public ?string $role, // nullable string
        /** @var array<int, string> */
        public array $permissions
    ) {}
}

Você executa um comando simples: php artisan typescript:transform.

E esse arquivo é criado automaticamente em:

export type UserData = {
    id: number;
    name: string;
    email: string;
    role: string | null;
    permissions: Array<string>;
}

Olha essa precisão! Ele tratou ?string como string | null. Entendeu o PHPDoc do array. Eu não escrevi um único caractere disso.

Refatorando o componente de frontend

Agora, vamos olhar para um componente React usando Inertia. Antes, eu ficava chutando o que existia dentro de user. Agora, tenho suporte completo da IDE.

import React from 'react';
import { UserData } from '@/types/generated'; // Importado do arquivo gerado automaticamente

interface Props {
    user: UserData; // Tipagem estrita!
}

export default function UserProfile({ user }: Props) {
    // O autocomplete funciona aqui!
    // Se eu digitar "user.", o VS Code sugere "email", "role", etc.
    
    return (
        <div className="p-4 shadow">
            <h1>{user.name}</h1>
            
            {/* Erro de TypeScript: a propriedade 'isAdmin' não existe no tipo 'UserData'. */}
            {/* O compilador detecta meu erro de digitação antes mesmo de eu salvar o arquivo! */}
            {user.isAdmin && <span className="badge">Admin</span>} 
            
            {/* Uso correto com base no DTO */}
            {user.role === 'admin' && <span className="badge">Admin</span>}
        </div>
    );
}

O workflow do “Ih, quebrei tudo”

O verdadeiro valor dessa abordagem aparece quando você refatora.

Imagine que você decide que permissions não deve mais ser um array de strings, mas sim um array de objetos. Você altera o DTO em PHP. Executa o comando do transformer. Imediatamente, o terminal fica vermelho. O build do React falha. O TypeScript aponta exatamente cada arquivo do frontend onde você estava fazendo map em permissions esperando uma string.

Você não precisa sair caçando bugs. O compilador te entrega uma lista clara do que precisa ser corrigido. Essa tranquilidade não tem preço.

Lidando com Enums

E fica ainda melhor. Isso funciona perfeitamente com Enums do PHP.

Agora, a lógica do frontend não consegue, por acidente, verificar um role que não existe. Você efetivamente erradicou as “magic strings” de toda a sua base de código — da coluna no banco de dados até a condição no JSX do React.

#[TypeScript]
enum UserRole: string
{
    case Admin = 'admin';
    case Editor = 'editor';
    case Customer = 'customer';
}

Fica:

export type UserRole = 'admin' | 'editor' | 'customer'

Uma linguagem para governar o mundo tudo

Falamos muito sobre “Backend” e “Frontend” como se fossem dois mundos diferentes. Mas em uma aplicação monolítica Laravel + Inertia, eles compartilham o mesmo domínio.

Ao usar spatie/laravel-data junto com o typescript-transformer, você constrói essa ponte. Você deixa de ser apenas um desenvolvedor PHP ou um desenvolvedor React; você passa a ser um Arquiteto de Aplicações.

Sim, configurar esse pipeline leva uns 30 minutos. Mas as horas que você economiza depurando erros como “undefined is not an object” são incalculáveis. Pare de escrever interfaces manualmente. Deixe a máquina fazer o trabalho.

Compartilhe com quem você gosta