Enhancing client outcomes with Type safety in Laravel

Enhance client outcomes and streamline your Laravel development process with type safety, and automated transformation of DTOs to TypeScript types.

The initial problem

In the dynamic world of web development, miscommunication between the backend and frontend can lead to significant issues - bugs, delays, and increased frustration. It's not uncommon to face issues where the frontend expects certain attributes and relations from the backend, only to find out that they are not present in the API responses. This misalignment results in impact on project timelines and quality.

Our approach

To tackle the ambiguity in communication, we insisted on implementing a system that would enforce strict validation of data structures within our backend code. This meant defining a clear and standardized structure for API responses, leaving no room for uncertainties or discrepancies. By solidifying the data structures at the source, we aimed to establish a foundation where the backend would dictate the expected format, ensuring that the frontend could confidently rely on the incoming data.

Recognizing the manual effort and potential for errors in maintaining TypeScript types alongside our backend Data Transfer Objects (DTOs), we sought an automated solution. Our expectation was to seamlessly transform DTOs into TypeScript types. This automatic transformation not only promised to reduce the burden of manual type maintenance but also ensured that any changes made in the backend would effortlessly propagate to the frontend, eliminating the risk of inconsistencies and developers working with confidence and efficiency.

An impactful solution

Our journey to enhance backend-frontend communication led us to the discovery of two powerful packages: spatie/laravel-data and spatie/laravel-typescript-transformer. These packages seamlessly aligned with our expectations, providing a robust solution that addressed our concerns and elevated our development workflow.

Strict Validation of Data Structures with spatie/laravel-data:

The cornerstone of our solution lies in the spatie/laravel-data package. This gem empowers us to define the structure of our API responses directly within our backend code. By utilizing this package, we can enforce strict validation of data structures, ensuring that all necessary attributes and relations are present in the output data. This approach not only eradicates miscommunication between the backend and frontend but also fosters a clear and standardized representation of our data.


Automatic Transformation of DTOs to TypeScript Types with spatie/laravel-typescript-transformer:

Complementing our efforts, the spatie/laravel-typescript-transformer package introduces a time-saving feature – the Typegen. This feature automates the transformation of Data Transfer Objects (DTOs) into TypeScript types. Now, the tedious task of manually maintaining TypeScript types is replaced with an automated process. The Typegen feature ensures that most types used in our project are automatically generated from the backend code, creating a seamless flow of types between our backend and frontend. Any changes made in the backend code are effortlessly reflected in the frontend types, eliminating the risk of inconsistencies and enhancing overall type safety.

Together, these packages provide a holistic solution to our initial problem. By implementing spatie/laravel-data for strict validation of data structures in the backend and leveraging spatie/laravel-typescript-transformer for automatic transformation of DTOs to TypeScript types, we have successfully bridged the gap between our backend and frontend systems. In the upcoming code snippets, we'll delve into practical examples, showcasing how these packages work in harmony to create a robust and efficient development environment.


Example code snippets

Step 1: define DTOs to ensure a structured data flow

use App\Http\Data\Project\Partials\ShowProject;
use Spatie\LaravelData\Data;

/** @typescript */
class Show extends Data
{
   public function __construct(
       public ShowProject $project,
   ) {
   }
}
use App\Enums\ProjectStatus;
use Spatie\LaravelData\Data;

/** @typescript */
class ShowProject extends Data
{
   public function __construct(
       public string $id,
       public string $title,
       public string $description,
       public ProjectStatus $status,
       public ?string $start_date,
       public ?string $end_date,
       public string $created_at,
       public string $updated_at,
   ) {
   }
}

 

Step 2: Initialize DTOs to streamline data handling

use App\Http\Data\Project\Partials\ShowProject;
use App\Http\Data\Project\Show;

class ProjectController extends Controller
{
   public function show(ShowProjectRequest $request, Project $project): JsonResponse
   {
     $project->load(['features']);

       return Response::json(new Show(
           project: ShowProject::from($project),
       ));
   }
}

Note that if we don’t load the `features` relation, we won’t be able to fill the expected features attribute expected in the DTO and the laravel-data package will throw a validation error.

 

Step 3: Generate the TypeScript types

Generate the types using the artisan command provided by the patie/laravel-typescript-transformer package:
'php artisan typescript:transform --format`

// resources/js/utils/types/declarations/generated.d.ts

declare namespace App.Data.Project {
 export type ProjectFeature = {
   id: string;
   description: string;
 };
}
declare namespace App.Enums {
 export type ProjectStatus = 'concept' | 'published' | 'ready';
}
declare namespace App.Http.Data.Project {
 export type Show = {
   project: App.Http.Data.Project.Partials.ShowProject;
 };
}
declare namespace App.Http.Data.Project.Partials {
 export type ShowProject = {
   id: string;
   title: string;
   description: string;
   status: App.Enums.ProjectStatus;
   start_date: string | null;
   end_date: string | null;
   features: Array<App.Data.Project.ProjectFeature>;
   created_at: string;
   updated_at: string;
 };
}

 

Step 4: Implement the generated types in the frontend, enhancing development speed and reducing errors

const fetchProject = async (id: string) => {
 try {
   const url = route('project.show', { project: id });
   const response = await fetch(url);
   const result: App.Http.Data.Project.Show = await response.json();
   return result.project;
 } catch {
   return null;
 }
};

const project = await fetchProject('419984c2-66a1-4c16-b446-c0ce8b4d4506');
console.log(project);
// {
//   id: '419984c2-66a1-4c16-b446-c0ce8b4d4506',
//   title: 'Payroller',
//   description:
//     'A Smarter Payroll Platform Empowering Businesses of All Sizes',
//   status: 'published',
//   start_date: '2016-01-01 00:00:00',
//   end_date: null,
//   created_at: '2024-02-05 09:35:08',
//   updated_at: '2024-02-05 09:35:08',
//   features: [
//     {
//       id: '5f8afe75-00a2-48d2-916f-c423fe2fb8b2',
//       description:
//         'Custom "Dimona" integration with Belgian Social Security Agency',
//     },
//     {
//       id: 'f8c545c9-4c55-4b35-8dd9-2bd014dfaeaf',
//       description: 'Prato integration (REST API / Azure service bus)',
//     },
//     ...
//   ],
// };

const title = project?.title; // string | undefined
const body = project?.body; // Type error: Property `body` does not exist on type `ShowProject`

 

Conclusion and client benefits

By combining the powers of spatie/laravel-data and spatie/laravel-typescript-transformer, we have successfully enhanced the communication and collaboration between our backend and frontend teams. Furthermore we've not only improved communication between our backend and frontend teams but have also significantly elevated our project delivery quality.

Preventing miscommunication, increasing type safety, and simplifying frontend development are just a few of the advantages that have significantly improved our Laravel development workflow.

These innovations lead to fewer bugs, reduced error rates, and faster project completions, directly benefiting our clients with more reliable and efficient web solutions.

Embrace this approach to experience a streamlined, more predictable, and error-resistant development process in your Laravel projects, ultimately delivering superior outcomes to your clients.

Niels Quirynen