تماشای این درس نیاز به اشتراک حرفه‌ای دارد.

Why Build a Public API0:00

All right, so let's talk about adding an API to your application that is accessible to users out on the broad internet. The biggest question is, well, why would we want to do this? Again, remember, this is all around what do users need that we're trying to provide here. And the first response most people are going to give is, it's just kind of what you do, you know, you just build an API. We hear a lot of people talk about wanting to build APIs because this idea of API first means, well, if I build an API, then later when I need an API, I'll have one. A lot of people even decide to build single page apps purely just because that's an easier way to have an API at the beginning when you want one later. What I'm going to say right now is the number one best reason to build an API is because there's an actual, tangible, practical reason for that API, whether you have a particular mobile client that needs it, or you have some other site you want to integrate with. But there is one other good reason to build an API, which is that you want the information that you have on your application to be accessible publicly on the internet. And you want it to be there, discoverable and ready to use before you even know when it's usable. The number one circumstance for this is if you want to put some public information out there and maybe give a little bit of extra information for people who authenticate.

And you want it to be there, discoverable and ready to use before you even know when it's usable. The number one circumstance for this is if you want to put some public information out there and maybe give a little bit of extra information for people who authenticate. So let's set that up for Symposium. With a tool like Symposium, you usually have at least two sections of your API. One of them is going to be the publicly available, anybody can get this data. And one of them is going to be the, if I authenticate in, I can either get that same data, but at a better rate limit, or maybe I can actually get some specific data that is relevant just to me or that only I have access to. So if we take a look at Symposium, the types of data we have here, the main things we have are conference and speakers in terms of a public view. And so what we're showing and making accessible before you log in, which is a list of conferences and a list of speakers, are the things that we might consider exposing into our API.

And so what we're showing and making accessible before you log in, which is a list of conferences and a list of speakers, are the things that we might consider exposing into our API. Now, the conference list that we have is really just scraped from everywhere else. So it doesn't seem like it's something super useful for us to expose. You can get all those same conferences from looking at other APIs. But this list of speakers who have a public profile in Symposium, this doesn't exist anywhere else. And this could be interesting for somebody to consume. So the first thing I would say we should do is we should give out a list of the speakers and also all the talks that they've made public. But what if I also want to make my specific talks available to a tool and maybe available in a way that isn't exposed publicly here? For example, if you go to Symposium itself, you can see that there's a tool called Pronto.

Design Public vs Auth Endpoints2:19

But what if I also want to make my specific talks available to a tool and maybe available in a way that isn't exposed publicly here? For example, if you go to Symposium itself, you can see that there's a tool called Pronto that allows you to use a browser plugin that they built to more easily submit your talks to various websites that are structured a certain way for allowing you to submit to a CFP. Well, Pronto authenticates against Symposium, pulls in all your favorite talks, and allows you to easily submit them here. And those talks may not be in your public profile. So we also need the second authenticated version of the API to make a tool like this work. So for that, we're basically saying we are going to have two endpoints. Our first endpoint is going to be speakers and our second endpoint is going to be an authenticated endpoint, which is my talks. And I mentioned rate limiting and something to think about with rate limiting is it's very common for an API to say anybody on the internet can have up to, let's say, 10 requests a minute for the speakers list or for any other endpoint that you have. But if you're authenticated, you can get up to 100 or up to 1000 a minute or some other constraint.

And I mentioned rate limiting and something to think about with rate limiting is it's very common for an API to say anybody on the internet can have up to, let's say, 10 requests a minute for the speakers list or for any other endpoint that you have. But if you're authenticated, you can get up to 100 or up to 1000 a minute or some other constraint. And so authenticated users, now we can say, oh, this person is someone I can control more or I can give more freedom. Whereas public access, somebody can be doing something nefarious and we have to be much more controlled and locked out about what they're doing. So the authentication is actually going to impact this API in two ways. One, it's going to allow me to access my data more in a more deep and nuanced way. And two, if I'm authenticated, I can get a better rate limit. So let's really quickly take a look at what it's going to look like for us to build out some routes. So there used to be routes/api.php by default. It's not going to be there by default.

So there used to be routes/api.php by default. It's not going to be there by default. We're going to install it in just a little bit. So we'll get there. But just by default, let's assume that we were in there. Let's say Route::get('api/speakers'). We were just to build an endpoint here that allows us to get those speakers. So there's a couple of things we'd want to do here. We want to make sure that we authenticate. We want to make sure that we add rate limiting.

Choose API Authentication4:14

We want to make sure that we authenticate. We want to make sure that we add rate limiting. We want to make sure that we return JSON. So we need to be able to figure out how are we going to do each of these things individually in the API in a different way than we're going to do in a normal web route. It just so happens that authentication, as we're going to set it up, is actually going to make it a little bit easier for us to do the others. So let's go take a look at authentication first. There are two main tools you might use for authentication with your APIs, and they are Sanctum and Passport. Let's start with Laravel Sanctum and then go to Laravel Passport. Laravel Sanctum is a tool that makes it very easy to set up two very, very common API authentication patterns. The first one is you want to create something called a personal token, which basically means somebody says, hey, I just want to log into your website.

Laravel Sanctum is a tool that makes it very easy to set up two very, very common API authentication patterns. The first one is you want to create something called a personal token, which basically means somebody says, hey, I just want to log into your website. I want to create a token, and then I want to use that token somewhere else to access it. It's something that GitHub does and a lot of other tools do where it's not this full complex OAuth flow, if you're familiar with that concept. The other thing that Sanctum does, though, is if you're building a single page app, it allows the single page app that's hosted in your Laravel app to talk to your API in your Laravel app using just the classic authentication guards in Laravel without all the complexity of building in an OAuth workflow and the tokens and everything like that. It just works. So if you're building an SBA, you definitely want to use Sanctum. But even if not, as long as it can just use a personal access token where one person logs in and says, here's my token, and I'm going to share it with this other tool, or here's my token, and I'm going to put it into my tool or my .env file or whatever else, Sanctum is the way to go. If you need something a little bit more complicated, Laravel Passport is the easiest way I've ever seen to set up OAuth. You've probably dealt with OAuth before.

If you need something a little bit more complicated, Laravel Passport is the easiest way I've ever seen to set up OAuth. You've probably dealt with OAuth before. It is when you're logging into something, and you click on the thing, and it takes you over to the authentication website. So let's say you're logging in with GitHub like we set up earlier in this course, and you get sent over to GitHub's website, and it says, are you sure you want Symposium or whatever else to have access to your data? And if you say yes, now your GitHub and your Symposium are interrelated, and they're connected, and they can trade information back and forth. That is OAuth. It's much more complicated to set up. Passport makes it as easy as I've ever seen it be, but it is still a lot of work, and the vast majority of applications don't actually need it. Most applications are perfectly fine with Sanctum, so we're going to stick with that. So interestingly, when you go to the installation instructions for Sanctum, it actually says you should run php artisan install api, which doesn't just install Sanctum.

Install Sanctum and Tokens6:32

Most applications are perfectly fine with Sanctum, so we're going to stick with that. So interestingly, when you go to the installation instructions for Sanctum, it actually says you should run php artisan install:sanctum, which doesn't just install Sanctum. It also sets up your application for an API. If you had used Jetstream to start up your application, you would have had all this stuff already built in, but because we use Breeze, we need to install this ourselves. So we run our php artisan install:sanctum. It installs Sanctum for us. It's published a new database migration for us, but before we run it, I'm going to actually take a look at it, and then we can run it. So we're saying no for right now. And let's take a look, and you can see it says, please add the Laravel Sanctum HasApiTokens trait to your User model. So, oops, I'm going to say git status.

And let's take a look, and you can see it says, please add the Laravel Sanctum HasApiTokens trait to your User model. So, oops, I'm going to say get status. So it's modified composer.json and .lock because it's required Sanctum. It has added a config/Sanctum file for our Sanctum configuration. It's added a migration to create a personal access tokens table, and it's added a routes/api.php file. And that's why I said earlier, we don't need to worry about setting that up because the moment you run this install --api command, you're going to get access to it there. So let's follow its directions. We need to add the HasApiTokens trait to our User model. All right, we've got it. So now we can migrate our database.

All right, we've got it. So now we can migrate our database. Let's just do a php artisan migrate --fresh --seed just to be safe. So we need to create our User. Remember, we were using GitHub authentication to create our User. All right, so now that this User exists, I believe it's going to be our second User. We can go into tinker, and we can say User::all(). Yep, so our second User is this one that we just pulled down. User::latest()->first(). All right, so we've got our User here, and there's some fun things we can do in here.

latest first. All right, so we've got our User here, and there's some fun things we can do in here. But one of them is we can get all that user's tokens, which is nothing right now. We can also create tokens on this User. So we can say createToken. And the only thing you need to pass to createToken is the name of the token. So my first personal access token. And we've now created a token, and we've also gotten the actual plain text version of that token that we can copy for use later. All right, so now that we have Sanctum installed, we've got a token set up for our User. We can now go to our routes, and we can make sure that they are protected.

All right, so now that we have Sanctum installed, we've got a token set up for our User. We can now go to our routes, and we can make sure that they are protected. As you can see, it's already going to apply this auth:sanctum middleware for us. So we can see that any routes we do want to protect with Sanctum, we can just run this middleware called auth:sanctum on them. So for example, if we did want to add a route for speakers, and we didn't want to protect it against access from people who are unauthenticated, we're not going to run that middleware on it. So let's just have it return something default and make sure we're actually able to access it. We're going to pull up our API client.

So let's just have it return something default and make sure we're actually able to access it. We're going to pull up our API client. We're going to type in symposium.test/api/speakers, and let's hit it. This is not registered, and I expected the php artisan api:install command to register that api.php file, but let's check to make sure it's actually even registered to give us routes. So let's say route:list --except-vendor. And we don't see these API routes in there, so it must not be registered. So let's go over to bootstrap/app.php. And yep, so I would have expected it to register itself here. It didn't. I don't know if that's a bug or if that's intended, but we'll just add it.

And yep, so I would have expected it to register itself here. It didn't. I don't know if that's a bug or if that's intended, but we'll just add it. So API is going to be in the api.php file right there. Okay, so let's run route:list again. And now we see API/speakers, API/user. All right, so let's go back to our client and send that request again. And there we go. We got a HTTP OK, and it returns back speakers. Now let's see what happens unauthenticated if we ask for a user. And it says, oh, I'm trying to log in. So first of all, that shows that Auth Sanctum is working.

And it says, oh, I'm trying to log in. So first of all, that shows that Auth Sanctum is working. Second of all, it shows that our request right here is not requesting application/json. So we need to instruct it that it accepts application/json. And this is basically saying, hey, if you're going to send me anything back, I'd rather you send me JSON, not HTML. And there you go. Now you have a nice JSON response. So we've, and our speaker's endpoint is not asking for or caring for what you accept. It's just giving a string back, actually. So we've now been able to see that the speaker's endpoint is working,

It's just giving a string back, actually. So we've now been able to see that the speaker's endpoint is working, that the user endpoint is working, and that the Auth Sanctum protection is working. Let's take a quick test to authenticate against this to see if we can get it to work. So for that, we'll want to say authorization, and then the value will be bearer space, and then your personal access token. So bearer space, that token that we copied earlier. And let's see if it works. And there we go. Now that we have passed in a valid bearer token, we can get all of this person's information back directly from this endpoint right here.

Add Route Rate Limiting11:43

And there we go. Now that we have passed in a valid bearer token, we can get all of this person's information back directly from this endpoint right here. So let's set up our rate limiting now. Rate limiting basically allows you to keep individual people from hitting certain routes more than a certain number of times every minute or whatever else. And there's a rate limiter built into Laravel, but we want the rate limiters that's specific to routing. And so you can define a rate limiter here with a given name and define what that rate limit is. So for example, this default one that says for every minute, there should only be a maximum of 60 requests grouped by either the user ID if they're logged in, or if they're not logged in, grouped by their IP address.

there should only be a maximum of 60 requests grouped by either the user ID if they're logged in, or if they're not logged in, grouped by their IP address. But this right here is what we were talking about earlier. If the user's logged in, then we're going to limit them to 100 requests per minute by their ID. And if they're not logged in, we're going to request this IP address to 10 requests per minute. And that looks exactly like what we're looking for. So if you can see, this guy is telling us you're going to do it in the boot method of your AppServiceProvider class. So let's head over there. And we can paste it in and rename it. We'll call this one public API.

And we can paste it in and rename it. We'll call this one public API. So we can go take a look at the documentation. It'll show us that you can apply your rate limits to a route using the throttle middleware. And what comes after the colon is the name of your rate limiter. So we can grab this, and we'll wrap this entire API in it for now. And we called this public API. Easiest way to handle that is just to not authenticate, and then try it 10 times. Oh, well, try one. And there you go. Too many attempts.

Oh, well, try one. And there you go. Too many attempts. So if we ask for it for more than 10 times within the minute, we're going to get this too many attempts error. And if we want to use it again, we'll have to wait for another minute for it to reset. So let's build out what would actually be happening here. Now, in the real symposium, we allow the User to define whether or not they want to be listed on the public speakers list. And for each of their talks, they can define whether that talk should be there publicly. And we didn't have time to build that out in this course. So if you were to build something like this, the basic way you do it is on the User, they would just have a boolean that says, wants to be shown in the public, you know, yes or no, and allow them to change that.

Structure Responses with Resources13:59

So if you were to build something like this, the basic way you do it is on the User, they would just have a Boolean that says, wants to be shown in the public, you know, yes or no, and allow them to change that. And in the talk, you'd also say, does this talk go in the public list, yes or no? Right now, we're just going to say, give me all users. And we'll get their talks later. So the simplest way, and this is not the best way to do it. The simplest way to return data from your endpoint is literally just to return an Eloquent Collection or an Eloquent Object, which will automatically be converted to JSON. So if we go to speakers right here, you can just see every single User. We're just going to get the full User. You don't have a lot of control over this.

We're just going to get the full User. You don't have a lot of control over this. You can't choose which HTTP status you're using. You can't set specific headers. You can't define which of the parameters are here. It's not great, but at least it gets you started. The next most powerful thing you can do is say response()->json(). And then inside of there, you can pass in the data. And then you can also pass in, for example, status, which you can see defaults to 200. So you can say whatever, you know, whatever is the appropriate status.

And then you can also pass in, for example, status, which you can see defaults to 200. So you can say whatever, you know, whatever is the appropriate status. So we still end up getting that same thing, but we now have that response to a one and we can start structuring the data a little bit more. The best way to do this is to use something called API resources. Laravel has this concept called API resources that is around this idea that you want to build a thing that is coming back from your API, whether one thing is coming back or a bunch of those things are coming back. And you want to kind of build a PHP object whose responsibility is for structuring what that thing looks like. So we're going to make a resource for a Speaker. We're going to say php artisan make:resource SpeakerResource. SpeakerResource.

We're going to say php artisan make:resource SpeakerResource. SpeakerResource. And so if we open up this SpeakerResource file, if I want to convert something to an array by default, it's just going to use whatever the native one is. But if we want to customize it ourselves, we can say something like this. Let's say we want to have their name. So we'll say name and use this, even though we're not on the User object here, it's going to proxy that call down. So we can say this name and we just keep it that. Let's just see what happens when you do that. So when you're returning this here, instead of returning this, again, I should be showing you the documentation.

Let's just see what happens when you do that. So when you're returning this here, instead of returning this, again, I should be showing you the documentation. They show you that you are going to return if it's a single one, you're going to return a new instance of the UserResource, and then you're going to pass the $user in. If it's a collection, you're going to say UserResource::collection. So we're going to say SpeakerResource::collection, and then pass in that $user all call that we were doing. So let's see what we get there. Needs two colons. And there we go. We have our two users, both of which have the name of Matt Stauffer.

And there we go. We have our two Users, both of which have the name of Matt Stauffer. And so we've structured the fact that we can define that regardless of what you have on your eloquent object, when we are returning them as a speaker, this is what we want them to have. You can take a look at the documentation to see all sorts of other things that you can customize in here. For example, you can change the shape. The shape by default is this pretty common API shape where everything lives under data. And then some other things that are like metadata and links might live outside of data. So you can customize all that. You can choose how the keys are sent over. But one of the things that I think is very interesting is you can also build in the relationships.

You can choose how the keys are sent over. But one of the things that I think is very interesting is you can also build in the relationships. So if you want to have all the users talks, you can have a talks parameter. So you can really quickly say make resource TalkResource. And then when you say talks, you can say TalkResource collection. This talks and this is just the talks relationship on the User. And then we now will get all that users talks embedded. And you can also just like we did with the SpeakerResource, you can go to that TalkResource and you can customize what data it has in it. So this is a really simple, but very powerful way for you to customize exactly what data you want to send over. So let's go ahead and run this.

So this is a really simple, but very powerful way for you to customize exactly what data you want to send over. So let's go ahead and run this. And also just like we did with the speaker resource, you can go to that talk resource and you can customize what data it has in it. So this is a really simple, but very powerful way for you to customize exactly what the embedding of your API resources looks like. If you need something that is even more complicated, there's a php package called fractal, which also has a layer of all wrappers. You can just look up layer of all fractal. And is a much more complicated, much more powerful structure for building just super nuanced API endpoints and what they're going to return, how they're embedded and everything like that. I found it's over complicated for a lot of APIs. But especially if you're working with a bigger team, if you have to work with specific standards like how our JSON API or anything like that, fractal is a really fantastic way to go about it. One other really helpful resource, if you are interested in allowing users to pass in things like ?filter=this ?order=ascending or ?location=near whatever.

But especially if you're working with a bigger team, if you have to work with specific standards like how our JSON API or anything like that, fractal is a really fantastic way to go about it. One other really helpful resource, if you are interested in allowing users to pass in things like ?filter=this, ?order=ascending or ?location=near, whatever. If you're trying to do any customization and parameters, it can be kind of overwhelming to build that tooling and also to decide kind of what the best syntax is for that. I'd like to look to JSON API standard for what the best syntax is there. But one of the easiest ways to do that in Laravel is using spatie/laravel-query-builder package, which makes it super easy to set up things like all those kind of filters that you might want to do. So when you're defining it, you can say, well, what columns are they allowed to filter by? And then it's just going to pull those things directly out of the URL. So you can say, because we said name is an allowed filter, you can say users where filter[name]=John. And this particular syntax right here is a very common syntax.

So you can say, because we said name is an allowed filter, you can say users where filter name equals John. And this particular syntax right here is a very common syntax. And all the syntax is that the layer of query builder provides for you are very common. And I would definitely even if you don't use this tool, which you should, it's a really good place to look to for inspiration for what common API filtering, sorting and everything else syntaxes are going to look like. It also has include so you can say /users doesn't include their posts. But users question mark include equals posts does include their posts. That's one of the ways that JSON API and other API specifications recommend that you don't give them too much data and take too long unless they actually need it. So definitely check out this package. It's great in tandem with some of these other tools like fractal and the API resources. So I definitely would recommend you trying out for any APIs.

It's great in tandem with some of these other tools like fractal and the API resources. So I definitely would recommend you trying out for any APIs. So the only thing we haven't really built out here is the idea of what that pronto tool I was showing is going to give you, which is the ability to see all of my talks when I'm logged in. So there's two ways to do it. One of them is we could just make sure that we embed the users talks in here. We can say, you know, something like a UserResource and make a custom API resource that embeds all the users talks. Or we can make a second round here that would be basically userTalks, and it would return just like this, except it would be a TalkResource. And that TalkResource collection would just be the talks for this logged in user. However, you want to handle it. So we have an API that is rate limited that has some of it accessible to the public, but it'll be differentially rate limited depending on whether or not you've logged in.

Documenting the API21:03

However, you want to handle it. So we have an API that is rate limited that has some of it accessible to the public, but it'll be differentially rate limited depending on whether or not you've logged in. Some of it is not accessible to public. You have to be logged in and it gives you specific information to you. We've got API resources structuring what the returns are going to look like. We've tested to make sure the authentication system works. We got a pretty powerful API working here. So now how do we document it? That's a big open question right now, and there's no clear winner in the Laravel community. So I'm going to give you a few common options.

That's a big open question right now, and there's no clear winner in the Laravel community. So I'm going to give you a few common options. The most common option for getting started is literally to just create a new folder called docs and just put Markdown in it. Because most of us are working in GitHub. If you just put a docs directory. What the heck? Just docs. Thank you. And then there you add a file. That's something like index.md.

And then there you add a file. That's something like index.md. You can write your docs, API docs, and then you can say, you know, give all your instructions. You can give lots of examples for everything, you know, for what the responses should look like or whatever. And then when you open this up in GitHub, you're going to get a nicely treated preview that is accessible to everybody internal. If you have a closed source tool, however, you can't just use that. There's a couple different options available to you in terms of the options that are available for really big, robust generated sites where it's automatic. At Tighten, we haven't found a really big winner, but Scribe does keep coming up as one that people are excited about. Scribe generates the API documentation for you from annotations in your code base, and they're going to look like this. So your code base gets a lot junkier as a result, but you do end up building some pretty powerful documentation as a result of it.

Scribe generates the API documentation for you from annotations in your code base, and they're going to look like this. So your code base gets a lot junkier as a result, but you do end up building some pretty powerful documentation as a result of it. And one benefit of using something like Scribe is it also generates a collection for Postman and a spec for OpenAPI. If you want to in-between spot, Jigsaw, which is a static site generator that we maintain at Tighten, has presets. And one of the presets is for API documentation. So if you do Jigsaw and it docs, it's going to build you a really powerful documentation template that is built with the intention for you to use it to build documentation out for your application. Or your API. So if you want to write it in Markdown, you want to host it publicly, and you're not interested in like Scribe, you're doing all the inline annotations, you want to build it yourself. This is a great way to get started. So all that's left is testing. If you go over to HTTP tests, there's a section called testing JSON APIs.

Testing JSON API Endpoints23:32

So all that's left is testing. If you go over to HTTP tests, there's a section called testing JSON APIs. You can see some examples here where you basically do something to your endpoint, whether you're going to get or you're going to post. And then you can make assertions against the response. Of course, you want to make sure you know your status looks a certain way. assertJson checks for a fragment, so it doesn't check that this is exactly what the JSON is structured like. It just checks to make sure that this exists somewhere in there, which is usually good enough. So if you want to say, for example, assertJson that we have a User with ID of four, you can do it really easily this way. If you need to be stricter, you can also assert for exact JSON matches. And then you can say assertExactJson, like this is exactly what your JSON comes back like. You can also reach down to a particular path and say assert that team.owner.name is equal to Darian, that you get a certain response back.

And then you can say exact JSON, like this is exactly what your JSON comes back like. You can also reach down to a particular path and say assert that team.owner.name is equal to Darian, that you get a certain response back. And then there's also this more complicated one where you're basically treating your JSON response as if it were an Eloquent response. Where this, this, where this, that, then you want to make sure something happens. You know, this is missing or whatever else. So for ours, let's take a look at what we might want to do. We probably are going to want to get to this one, which is asserting against a JSON collection. And in this one, you can say basically assert that it has a certain number of items. And then against the first one, for example, you can say make sure that it matches these particular parameters. And I think that's one of the best ways to do it.

And then against the first one, for example, you can say make sure that it matches these particular parameters. And I think that's one of the best ways to do it. If we go a little bit further, though, it's going to tell us what happens when we're working with JSON collection. And I think the easiest test for us to build out here is against the speakers endpoint, which is a collection. So you can see here, it's going to say, well, we want to make sure it has this particular, you know, key. And then this particular key has this many items. And then the first item of this one looks like this. That's obviously a much more kind of reasonable shape against what we're building here. So let's get started on that. Let's make a test, a SpeakerApiTest.

So let's get started on that. Let's make a test, a SpeakerApiTest. And we'll open up. All right, so we're going to say it gets /api/speakers. So we want to assert against the /api/speakers route. And we probably need to make some speakers first, so we can say app\Models\User::factory()->count(3)->create(); And now we can say assert that it has a data parameter with three items in it. And the first item in our data is going to be shaped a certain way. And so let's say $firstUser = User::first();

And the first item in our data is going to be shaped a certain way. And so let's say $firstUser equals User::first(). I guess I could have done that in this one too. So we want to make sure that the $name is equal to the $firstUser. We don't really have much else in that API endpoint, so we don't really want to worry about the rest of those. So we're just going to say where $name $firstUser. So let's try it out. There we go. And just to make sure that that works, let's just test the negative. Not this person $name.

And just to make sure that that works, let's just test the negative. Not this person name. And we get an error because it says property data. Oh, that name does not match the expected value. So while this is saying where, which makes you think it's a query that you're going to eventually cue things onto afterwards. This is actually checking to make sure that this is the case with our provided JSON. So there you go. You have built an API. It is returning JSON, which you can customize any way you want. You know how to accept query parameters for API calls.

It is returning JSON, which you can customize any way you want. You know how to accept query parameters for API calls. You've got rate limiting. You've got authentication authorization. And you can even write tests against it and document it. This is everything you need to build a very robust, very healthy public API for your new app.

دوست دارید گاهی خبرهای Laracasts را ایمیل کنیم؟