GraphQL, Laravel, and Vue Part 1: Backend

Brandon Shar
Dec 4, 2018 · 13 min read

This tutorial / blog post will walk you through setting up and using GraphQL in your Laravel + VueJS app, along with some of the patterns and practices that have pushed GraphQL over REST for me. It’s not meant to give you any deep level of expertise on these subjects, but more to show you enough to know if it’s worth a deeper look for your apps and projects and to demonstrate some of the power you get from using GraphQL.

This tutorial is intended to be a jack of all trades, master of none, so I won’t be explaining much of anything specific to Laravel or VueJS. I will try to spend a bit of time justifying GraphQL vs REST, why I selected certain packages, and why I use certain patterns, but I won’t be going into as much detail as I could on any one part . I always love talking about them though, so if a part of this tutorial interests you a lot, or if you find the motivations behind a part unclear, comment, message me, or open an issue on the repo; I’d be happy to write a more in depth article on part of it.

This post is broken up into two parts, Backend and Frontend, so if you’ve already set up one or the other, you can jump right to the part that matters to you. Click here to jump straight to the frontend portion with VueJS and Vue-Apollo.

The repo to follow along with is located here:

with each commit marking an individual section. I’ll include the commit hash before each section of code, so you can easily follow along without writing a line of code if you want.

I also highly recommend you use Google Chrome and install the Chromeiql plugin located here: https://chrome.google.com/webstore/detail/chromeiql/fkkiamalmpiidkljmicmjfbieiclmeij?hl=en

This will allow us to test and query our GraphQL api right in the browser without having to install and configure additional software packages.

All set? Then let’s dive in to the GraphQL backend with Laravel and Lighthouse!


Because we’re not diving into Laravel’s base functionality, I recommend cloning the example repo and checking out the commit right before we add GraphQL. I’ll explain a bit about the app so we know what’s going on.

git clone https://github.com/BrandonShar/laravel-vue-graphql-tutorial.git && git checkout a1f789f94801e7c887f2c9790e4807d99dab2e93

Then follow the usual steps to fire up a Laravel app

cp .env.example .env
composer install
php artisan migrate --seed

So what do we have here? I originally starting writing this app during the Thanksgiving holiday, so it ended up loosely Black Friday themed. We have a list of products, each with various prices and savings; we have users, each of whom may own a shopping cart; and we have the association of products to a shopping cart. Here’s a diagram to illustrate our data model:

Our seeders should have given us some sample data as well so we can always use tinker to get a better feel for the app.

php artisan tinker
$shoppingCart = User::first()->shoppingCart;
$shoppingCart->products()->attach(Product::first());
$shoppingCart->products;

So hopefully we’re all comfortable with our app and ready to talk some GraphQL!


Install Lighthouse: 1c9ff7ae09cd6b3b853446f28c7027c5da732b68

Let’s start by installing the server side GraphQL package we’re going to use. It’s called Lighthouse and has excellent documentation located at: https://lighthouse-php.com/. There are two main packages you’ll find when you google something like “laravel graphql”. I chose Lighthouse because it’s well-documented and has very little boilerplate. The one con is that it chooses to use a .graphql file instead of php for the schema, but that’s a small price to pay for the simplicity it brings.

composer require nuwave/lighthousephp artisan vendor:publish --provider="Nuwave\Lighthouse\Providers\LighthouseServiceProvider"

Configure Lighthouse: 2c3e64a83bca8adf9f3e84b454b8568d1726d0f3

Once we have the config published, there’s only a couple of minor changes we need to make in config/lighthouse.php.

First, let’s enable some middleware on line 45. GraphQL and Apollo can use any type of auth you like, so if you prefer tokens, that’s a-ok, but for our simple tutorial, we’re just going to add the `web` and `auth` middleware to authenticate our graphql calls the same way we authenticate the rest of our requests, with cookies.

Remember that since we added the auth middleware, we’ll need to login via our new Laravel app before running queries on our API. If you receive errors using GraphiQL / Chromeiql, this is a good thing to double check.

We’ll also want to change the namespace of our models on line 97. Lighthouse assumes our Models live in App\Models (which is actually my preference), but for simplicity’s sake we just kept our models in App. So let’s change that value to App.

Finally, all GraphQL requests, by convention, are POSTs to the graphql endpoint (defaulting to /graphql). Because they’re POSTs, we’ll need to either include a CSRF value or tell Laravel not to worry about it. In keeping with our trend of simplicity, let’s just go to app/Http/Middleware/VerifyCsrfToken.php and add "/graphql"to our $except array so that Laravel just lets those calls through.

Here are all of those changes as a diff, which is probably easier to follow along with:

If this has all gone well so far, we should be able to open graphiql, add our endpoint (As a Laravel Valet user, for me that’s http://laravel-vue-graphql-tutorial.test/graphql) and view the schema! It will only hold Lighthouse’s example set up, but that’s a start!


So what is GraphQL? We’re not going to dive in too deeply, but we should at least cover some basic concepts. To put it in their words, GraphQL is a query language for APIs. https://graphql.org/ is an excellent resource to learn more about the GraphQL concepts, so we’ll keep this description brief and in our own words. GraphQL is a way to specify exactly what data you want from the server. With REST, we have multiple endpoints that we access with various HTTP verbs like GET, POST, PATCH, and DELETE. These endpoints are typically set up based on certain conventions and each returns whatever data it wants, typically as JSON. GraphQL works quite a bit differently. With GraphQL, we only POST to the same endpoint each time and our payload contains instructions in the form of a GraphQL Query for what data we want to get back. We then get our data returned in the same shape as what we requested.

The GraphQL Query is a JSON-esque looking structure made up of strongly typed elements called Queries, Mutations, Types, and Fields. A Query is a read-only request and by convention it should never contain any side effects. A Mutation is a request that does contain side effects, maybe it modifies some data or sends an email. Both Queries and Mutations can return data in the form of Types. A Type is basically a description of the data and contains many strongly typed Fields. It’s easier to understand with some code, so let’s write our first type.


Add Queries and Types: 8a5cb3c35773322b30276887d3d497f16dcd9896

For Lighthouse, the GraphQL schema is all stored in routes/graphql/schema.graphql. Lighthouse’s docs explain how to separate this single file into multiple files if you prefer to organize that way, but we’ll just focus on this one file. Let’s delete all of the example content Lighthouse has added for us so that we have a clean slate.

Let’s start by adding a User type. In order to add a type, we’ll want to know what it’s called (in this case User), what fields we want to expose, and what type each of those fields is. The data we want to expose about a user is their id, name, email, and their shoppingCart. You should also always include an ID for every type because, as we’ll see later, Apollo needs it to perform some of its best caching features. For types, our id is an id type and the name and email are both strings. You can read more about scalar types on the GraphQL site here. So let’s see what we have so far:

type User {
id: ID!
name: String!
email: String!
}

Why the ! character after the type? This signifies to GraphQL that this is a required field. It doesn’t mean that you have to query the field, but it does mean that if you do, it must return a value of the listed type, never null. If there’s a possibility the field could be null, we wouldn’t use the !. This is more than convention; your GraphQL server will error if supplied a null value for a required type. This is great for your frontend though, because it will always know which data it should be prepared to handle null values in.

A user also has a shoppingCart. We’ll add that with

type User {
id: ID!
name: String!
email: String!
shoppingCart: ShoppingCart @hasOne
}

Let’s break down what we just added. @hasOne is a Lighthouse directive that helps the Lighthouse package optimize queries that involve relationships. So what is ShoppingCart? Well, it’s a type we have yet to add! So let’s add ShoppingCart now.

type ShoppingCart {
id: ID!
total: Float!
totalBeforeSavings: Float!
savings: Float!
products: [ShoppingCartProduct!]! @belongsToMany
}

This should look pretty similar to User with one exception: what the heck is the bolded line doing? Let’s break it down. The @belongsToMany is another Lighthouse directive for optimizing the eloquent query and the ShoppingCartProduct is another type we’re going to create. The [] wrapping it indicates that the field is going to be an array, and the two ! each indicate a different part of the field is required. The first ! on the inside of the brackets signifies that each element of the array will always contain a ShoppingCartProduct, never null. The second ! outside the brackets indicates that we will always receive an array when asking for this field. It might be empty, but it at least won’t be null. We have two more concepts we want to represent as types, so let’s add those now.

type ShoppingCartProduct {
id: ID!
name: String!
price: Float!
savings: Float!
quantity: Int!
}
type Product {
id: ID!
name: String!
price: Float!
savings: Float!
}

Why two different names for Product? Because we have two different ideas. One is the Product as a standalone model we can purchase. The other is the Product within a ShoppingCart that has the additional quantity field. GraphQL is an un-versioned API by convention, so it’s good practice to code a bit defensively and be willing to split concepts even if their data looks very similar. Even though they’re backed by the same model, we still have two distinct concepts here.

So now that we have all of our types, how do we get at them? Well, with one last type! Let’s add a Query type to serve as the root for our api queries. This query type will have two fields, me and products.

type Query {
me: User! @auth
products: [Product!]! @all
}

As you probably guessed, @auth is a Lighthouse directive that returns the currently authenticated user. We’re safe to use User! because we required auth in our middleware, but if this was a public api, you would probably remove that ! to allow the returned value of me to be null. Products is an array of Product objects that we’re just going to grab all of via the Lighthouse directive @all (it’s calling Eloquent’s App\Product::all() under the hood).

At this point, our queries are all set up! You should be able to go back into Graphiql (Chromeiql), refresh the endpoint, and read our documentation or make queries against our seeded data. If you’re getting an error here, don’t fret. Graphiql isn’t very clear about errors, but it generally means that we made a syntax mistake in schema.graphql. Take an extra look and compare it with the diff on Github to see if there are any differences. My most common mistake is adding a comma after each field out of habit. Also be sure to double check that you’re still logged in to your app.


Add Mutations: f1e67d24f533aa1fd4089280c9e018672debed6e

Now we’ve got this great read-only API, but most of the time we need our APIs to be able to make changes as well. For our’s specifically, we’ll want to be able to add and remove products from our shopping cart. GraphQL does this through mutations. A mutation is very similar to a query, the difference being (by convention) that a mutation has side effects (like modifying data) and a query does not. As far as naming conventions go, a mutation is very descriptive, single purpose, and should start with a verb.

Let’s add our first mutation:

type Mutation {
addProductToShoppingCart(productId: ID!): ShoppingCartPayload!
}
type ShoppingCartPayload {
shoppingCart: ShoppingCart!
}

So what all is going on here? First we’re defining our Mutation type and giving it a field of our mutation’s name: addProductToShoppingCart. We’re also specifying that our mutation has a mandatory, non-nullable argument called productId, that must be of type ID. We’re also returning a non-nullable response of ShoppingCartPayload, which is defined below the mutation as a new type.

Why return ShoppingCartPayload instead of just the shoppingCart or product? It’s more of our defensive strategy. By returning a payload that contains the shoppingCart field instead of the shoppingCart directly, we defend our ability to add more fields to this return type later without having to refactor our front end. Maybe later on, we want to return meta data like the last update date or the number of products in the cart. With this strategy, we’re fully backwards compatible and don’t have to worry about an awkward deprecation state.

So far all of our queries and types have been backed by Eloquent models, but this one looks different. Lighthouse has some nice directives to map mutations directly to eloquent queries, but I find that normal app structure often goes beyond the basic Eloquent create and update methods. Luckily, Lighthouse also offers a great way to handle these custom cases.

If we don’t have a model backing a query or mutation (like for this one) and aren’t using a directive like @auth, Lighthouse will look for a class with a certain namespace convention. Let’s create one of those classes for our new mutation with

php artisan lighthouse:mutation AddProductToShoppingCart 

It will live in app/Http/GraphQL/Mutations/AddProductToShoppingCart.php, which is exactly where our schema will be looking for it. This auto-generated class is well commented and contains a single resolve method with four arguments. The first, $rootValue will always be null in the case of root level mutations. The second, $args, will be an array of all of the passed in arguments. For our mutation, it will contain a single key called productId with the passed in value. $context is our shared data. The most valuable thing for us is that $context->user() will return us the currently authenticated user. Finally, $resolveInfo will contain info about the current query, which is most useful for things like optimization (eager loading queries you know you’ll need, for example). We won’t be needing it, so I’ve removed it, along with the comments, from my class.

Inside the resolve function, we add whatever code we need to perform our mutation and then return whatever data our payload needs (in our case, an array with a shoppingCart key). Here’s the code we’ll need to handle adding a product to our shopping cart. We don’t want to attach multiple times, so if we’ve already added a product, we’ll just increment the quantity. I’m going to link the classes on github, because the line lengths are a bit too long to make for a nice code-block here on medium.

And now our ShoppingCart class with the new addProduct method:

Awesome! At this point we should be able to refresh Graphiql (Chromeiql) and see documentation for our new mutation. Take it for a spin and try adding some products to your shopping cart!

Now that we’ve added some products, we’ll also need a way to delete them. Let’s repeat the above steps to also add a deleteProductFromShoppingCart mutation with the necessary logic. If you get stuck, as always, you can refer to the new code for each section in its commit.

Now we should be all set! If we reload graphiql again, we should see both of our newly documented mutations and be able to try them out. Just remember, Graphiql is not a sandbox; mutations we run will affect our real data.


Congratulations are in order, you just fully set up a GraphQL backend! Play around with Graphiql, query some things with our new api, tweet about your success (maybe include this article :fingers-crossed:), and give yourself a well earned break!

Before you go though, let’s recap just a bit. We fired up a new laravel app with auth, added some basic logic and a data model to handle a user’s shopping cart and products, then added a graphql front-end with queries and mutations. It’s important to keep in mind that other than the schema.graphql file, everything else was business logic. Even the contents in our resolve functions are just app logic, and in the case of adding products, we even pulled it out to make that more clear.

GraphQL is way of sharing data via an API and nothing more, so you don’t have to be afraid of whether it will work with your existing app: it will (or at least it can!). And without even touching the front end yet, we’ve already found at least one major advantage over REST: we automatically got 100% accurate documentation for every available action in our api. That’s a massive win and will make other developers (front end, other full stack, and future us) thank present us for not forcing them to dig through code to determine what all an endpoint can return.

So take some time to relax, think back through this code, and run some experiments of your own. I’d recommend checking out the Lighthouse docs as well, since there’s a lot we didn’t cover. Don’t worry, I’ll wait until you’re back before we start the front-end. Don’t wait too long though, because the front-end code is where GraphQL really starts to shine!

Click here whenever you’re ready to jump into the front end with VueJS and Vue Apollo!

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade