Despite working on numerous SaaS (Software-as-a-Service) ventures over the years, it wasn't until I started building Analyse, an analytics platform for game servers, that I began to realise how crucial end-to-end type safety is.
I would often hear about TypeScript from friends and "Tech Twitter", but I would often hesitate to use it as it felt like an extra step and I saw no need. I mean, I know what data I'm passing through, why do I need to spend extra time adding types to all my code?
But as the development of Analyse started, I was dealing with a lot more database models than any other SaaS project I'd worked on before. It became frustrating that I couldn't get auto-completion and type-hints from my code editor for these models, and I needed to refer back to my schema or models often to be sure I typed the column correctly.
It didn't help that I got the daunting squiggly lines from my code editor, "Property name
does not exist on type unknown
". Sigh.. but it does exist though, I know it does!
A typical setup
Consider a scenario where you have a User
model (I mean, who doesn't have users, right?), You'll typically pass this data from the backend of your Laravel app to your Vue.js component with Inertia.js like so..
While this works, it’s not ideal. You'll need to constantly refer back to your User
model in Laravel to know what properties are available. There’s also a risk of accidentally exposing sensitive data like tokens, passwords etc. Not what we're looking for!
This is where Laravel Data comes in
Not all super heroes wear capes, others use Laravel Data instead! This package allows you to create data classes that act as simple containers for your data. We can install this package using composer:
Once it's installed, we can create a UserData
class by running the following command:
Then we can modify our new class to look something similar to this:
Wrapping our User model with UserData
Now that we've create a dedicated data class for our user model, let's modify our user route to use the new class - notice how we are wrapping our model using the ::from
syntax.
Yippee! Now we're only passing the id
, name
, email
, and is_admin
properties to the front-end. No more data leakage!
But, what about TypeScript..?
Don't worry, let's talk about that! Any eagle eyed viewer may have spotted the #[TypeScript]
attribute in our new UserData
class. This attribute allows us to generate TypeScript types automatically using the spatie/laravel-typescript-transformer
package.
Let's run the following command:
By running this command, it will generate the TypeScript type for UserData
like so:
Now going back to our Vue.js component, we can define a type hint for our user
prop using the TypeScript type we just generated. Let's modify our Vue component to look like so:
Now if we access the user
prop in our component, we'll get auto-completion and type hints for the id
, name
, email
, and is_admin
properties. No more squiggly lines!
What about relationships?
Hold up, this isn't an article about dating! Hoping of course that we are talking about models here, let's say that a user can create posts, we would have a posts
relationship. We can also create a PostData
class and to wrap the Post
model. This gives us control over which data is passed to the front-end and ensure type safety.
Let's create our PostData
class to look like so:
Then amend the UserData
class to include the posts
relationship:
This would now generate the following TypeScript type for UserData
:
Now we can access the posts
relationship in our Vue component with full type safety. Like so:
Taking it further with model permissions
I'm sure you're already blown away by everything so far, but what if I told you we could take this a step further by adding permissions to our models? Let's say you want the delete button to only be shown to users who can delete that post. Easy!
Install the based/momentum-lock
package using composer:
Then extend your data classes from the existing Data
class provided by Laravel Data to a DataResource
like so:
Finally, register the DataResourceCollector
in the TypeScript Transformer configuration file like so:
Now we can use the can
method in our Vue component to check if the user can delete the post:
Voila, that's it! You now have full type safety across your full-stack application. :tada:
Hope it helps you as much as it has helped me! Feel free to reach out to me on Twitter if you have any questions.