Updated by: Oluwole Opeyemi
The REST API design pattern is one of the best practices when building an API for the back end of any application. Its benefits supersede its downside. When fetching data from an extensive REST application with many database relationships, there can be an information overload problem. This post will explore
To follow along with this article, you need to have the following requirements:
In a typical REST API-based application, data is fetched from the back end and displayed on the front end. There is the problem of overloading information (mentioned earlier). I'll further explain this using an example.
A front-end developer wants to fetch just the username of a user in the database. He expects his response to look like this.
user :{
userName:"alloyking12300"
}
But instead he gets this:
user: {
userName:"alloying12300",
email:"testemail@email.com",
age: 21,
profile: {
img:"image/path",
profession:"developer"
}
}
Now you see, the response above is packed with a lot of data that we do not need. It is, of course, possible to display what you need on the front end and be done with it, but this has a massive impact on the performance of the application. In the quest to solve this, GraphQL was born.
Secondly, REST API applications have too many routes. In very complex applications, the routes can become challenging to handle and manage. GraphQL helps us tackle this too.
Note: Strapi v4 now is able to avoid the over-fetching and under fetching issue with the use of “populate” and “filtering”. You can learn more here.
GraphQL is a query language for your API and a server-side runtime for executing queries using a type system you define for your data.
GraphQL provides developers access to just the particular data they need in a specific request by using just one endpoint (I like to think of it as a "smart endpoint"), which understands what the front-end application expects and returns precisely.
The bulk of the technicalities of implementing GraphQL has been simplified when using Strapi.
Strapi is an easily customizable open-source headless CMS. Strapi’s API can return responses in both REST and GraphQL. In this tutorial, we will look at how GraphQL works in Strapi.
To explore the wonders of GraphQL in Strapi, we need a Strapi project to test. Let's proceed by creating a new Strapi project using the following command:
yarn create strapi-app my-project
# OR
npx create-strapi-app@latest my-project
This command will create a new Strapi project and launch it in your browser like this:
When you fill in this form, you will create your first Admin User. After that, the application will redirect to the dashboard.
We need content to test our GraphQL API. Let's create a blog collection type and add some blog posts to it.
From the left navigation menu, from the plugin section, click on Content-TypesBuilder. Fill in the form to create a new collection type.
We need to modify our blog content type further to contain the Title, Body, and Date fields. These are necessary fields that we need. Fill in the form and select these fields. You can use the images below for guidance.
Repeat the process in the image above for the rest of the fields. Then save. After saving, we should have a newly created Collection Type of Blog.
We have created our Collection Type. We need a few blog posts to be able to explore how GraphQL works in Strapi.
From the Collection Type section in the navigation menu, click on Blog → Add new blog post.
Let's create a blog post with some dummy text, as this is for educational purposes only
To use GraphQL in Strapi, we need to install the GraphQL Plugin using the command below:
npm install @strapi/plugin-graphql
On completion of the installation, our Strapi application is set for GraphQL. Strapi also has a Playground for testing GraphQL operations. We can access this Playground using http://localhost:1337/graphql
.
The GraphQL Playground has an inbuilt text editor for you to enter your GraphQL commands, a play button to run your code and a screen to display the return values, error, or success message.
Before we proceed to the Strapi playground to test, we need to grant the public user access to the Blogs collection type. Authentication will be covered later on in this post. For now, let's get started with the public user account. To do this, click on settings
→roles
→permissions
to grant all the necessary access to the blogs and save.
The Strapi GraphQl playground is a development environment for you to interact with your Strapi application using GraphQl. Let's proceed with CRUD operations on our blog content using the playground.
In order to interact with the GraphQL server, we either need to write a Query or a Mutation. A GraphQL query reads or fetches data from the server, while a mutation is used to write or post values. For easier understanding, you can think of it as GET request and POST request where Query is used to make GET request and Mutation to make POST request.
With that said, let's get started by reading from our GraphQL server. We need to write a query for this. The code below is a basic GraphQL query to fetch all Blogs from our Strapi backend. It returns the Id, Title, and Body.
query{
blogs{
data{
attributes{
Date
Title
Body
}
}
}
}
Enter the code in your Strapi playground, and it should fetch all Blog posts in your Blog collection
We can fetch a single blog post from the collection by passing along a unique key. In our case, we will pass along the id.
query{
blog(id:"1"){
data{
attributes{
Date
Title
Body
}
}
}
}
Remember when we talked about Queries and Mutations earlier? We will use a Mutation to create a new entry. Let's create a new blog post to continue.
mutation blogs{
createBlog( data: { Title: "Fifth Post", Body: " This is the fifth blog post. It was created using a graphQl mutaiton from the strapi graphql playground" }) {
data {
attributes{
Title
}
}
}
}
The function createBlog accepts input object, which accepts a data object with inputs to insert. This function derives its name from the name of the Strapi collection type. For instance, if we wanted to create a new user, it would be createUser instead. The Title and Body are corresponding fields in the Blog collection. In the case of a different collection type, they should correspond with the fields of the collection type.
We use Mutations for updating features too. Let's edit the post we just created like so:
1 mutation blogs {
2 updateBlog(id: "7", data: {Title: "Fifth Post", Body: " This is the fifth blog post. It was created using a graphQl mutaiton from the strapi graphql playground" } ) {
3 data {
4 attributes {
5 Title
6 }
7 }
8 }
9}
The updateBlog method accepts an input object that specifies the id of the post we intend to edit and the data in the where and data objects, respectively.
Let us delete this post to see if it actually goes away. The mutation below will do just that.
mutation{
deleteBlog(id: "5") {
data {
attributes {
Title
}
}
}
}
The delete blog method accepts an input object with an object that accepts the id of the post to be deleted. After a successful delete, the blog returns null.
In order for a user to access any protected route in Strapi, they need to be authenticated. We can implement authentication in our Strapi application using GraphQL too. Let's get started by creating a new user.
mutation {
register(input: { username: "alloyuser1", email: "email1@gmail.com", password: "password" }) {
jwt
user {
username
email
}
}
}
We used a new function here. The register function accepts an input object containing user details to be created.
After successfully creating a user, it returns the user object and a jwt token. We need the token passed along as an authorization header as "authorization": "Bearer YOUR_JWT_GOES_HERE". The Strapi GraphQL playground can be set in the HTTP header section. That way, it will be passed along with every request, and the user can access protected routes.
In the case of an already existing user, users can login to generate a token. Let's login using the details of the user we just created above.
mutation {
login(input: { identifier: "email@gmail.com", password: "password" }) {
jwt
}
}
This would also return a jwt token for access like so.
To make more complex GraphQL queries, we can apply some filters to the query. The following query syntax of how to use the Strapi GraphQL filter feature.
filters: { field: { operator: value } }
In the above syntax, field represents
the name of the collection type to filter, while the operator is the filter query to use. The following are some of the most popular filter operators.
Eq: The eq
operator is used to filter data that matches exactly.
Ne: The ne
operator filters data that does not match the specified value.
Lt: The lt
operator filters data less than the specified value.
Lte: The lte
operator filters data that is less than or equal to the specified value.
Gt: The gt
operator filters data greater than the specified value.
Gte: The gte
operator filters data greater than or equal to the specified value.
You can visit Strapi's official documentation page to learn more about the remaining strapi graphql filter operator.
To better understand how filters can be used, let's make a query to fetch all the Blog posts with id
greater than 2.
query {
blogs(filters:{ id: { gt: 2 }}) {
data {
id
attributes {
Title
Body
}
}
}
}
The above query will fetch all posts with IDs greater than 3
from the backend.
Let's interact with Strapi GraphQL from an external Vue.js application. To get started, we need to create a Vue.js application. You can create it using the following Vue CLI command:
npm create vite@latest vue-app --template vue
cd vue-app
npm install
Make sure you have the Vue CLI installed globally. If you need help installing the CLI or upgrading to the latest version of Vue.js, follow this tutorial here for details. After a successful installation, launch the project using the following command:
npm run dev
Now, you should be able to serve up the Vue.js application in the browser. Find a screenshot of my served screen below:
To interact with GraphQL from our Vue.js application, we need to install Apollo and query our Strapi GraphQL server using Apollo. Run the command below to install Apollo Client.
npm install --save graphql graphql-tag @apollo/client @vue/apollo-composable
After installing Apollo, open the project folder in your code editor. In this tutorial, we’ll use Bootstrap classes for our application styling. Open public→index.html, and add the following Bootstrap style to the head section of the page.
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65"
crossorigin="anonymous"
/>
add this before the closing Body tag of the page
<!-- built files will be auto injected –->
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4"
crossorigin="anonymous"
></script>
After the installation, next, we need to configure Apollo to work in our application. Edit **src**
→**main.js**
file and update it with the following code.
1 import { createApp, provide, h } from "vue";
2 import { DefaultApolloClient } from "@vue/apollo-composable";
3 import { ApolloClient, InMemoryCache } from "@apollo/client/core";
4 import App from "./App.vue";
5 import router from './router'
6
7 const cache = new InMemoryCache();
8
9 const apolloClient = new ApolloClient({
10 cache,
11 // uri: "https://rickandmortyapi.com/graphql",
12 uri: "http://localhost:1337/graphql",
13 });
14
15 const app = createApp({
16 setup() {
17 provide(DefaultApolloClient, apolloClient);
18 },
19
20 render: () => h(App),
21 }).use(router);
22
23 app.mount("#app");
The code above, URI: http://localhost:1337/graphql
, points to the Strapi GraphQL endpoint. I have my Strapi server running on that port. Do not close your Strapi project while working on Vue.js.
Now we have GraphQL configured and working in our app. Let's try fetching a post from our GraphQL backend. This will require a router feature. Let's install Vue router using NPM to continue.
npm install vue-router
Once it's done installing, we need to configure the router to work with our application effectively. Head over to router→index.js and add the following block of code
1 import HomeView from "../views/HomeView.vue";
2 import BlogView from "../views/BlogView.vue";
3 import { createRouter, createWebHistory } from "vue-router";
4
5 const routes = [
6 {
7 path: "/",
8 name: "home",
9 component: HomeView,
10 },
11 {
12 path: "/blog/:id",
13 name: "blog",
14 component: BlogView,
15 },
16 ];
17
18 const router = createRouter({
19 history: createWebHistory(),
20 routes,
21 });
22
23 export default router;
We have created two routes for our home page and blog page. Let's edit our App.vue component to work with our router. Add the following code to the App.vue file.
<template>
<div id="app">
<Nav1 />
<router-view></router-view>
</div>
</template>
<script>
import Nav1 from "./components/HolaComo.vue";
export default {
name: "App",
components: {
Nav1,
},
};
</script>
Notice the HolaComo.vue component. I added it for ease of navigation. Create the component inside the /components directory, as shown below.
Add the following code to the **HolaComo**
component to create a basic bootstrap navigation.
1 <template>
2 <div>
3 <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
4 <!-- <router-link class="navbar-brand" to="/">Home</router-link> -->
5 <button
6 class="navbar-toggler"
7 type="button"
8 data-toggle="collapse"
9 data-target="#navbarNav"
10 aria-controls="navbarNav"
11 aria-expanded="false"
12 aria-label="Toggle navigation"
13 >
14 <span class="navbar-toggler-icon"></span>
15 </button>
16 <div class="collapse navbar-collapse" id="navbarNav">
17 <ul class="navbar-nav">
18 <li class="nav-item">
19 <router-link class="nav-link" to="/">All Posts</router-link>
20 </li>
21 </ul>
22 </div>
23 </nav>
24 </div>
25 </template>
Let's make our actual GraphQL query from the Vue.js application to access data from the Strapi GraphQL server.
Let's group the component into **views**
folder. From the root directory, create a new folder name views and create HomeView.vue. Update HomeView.vue with the following code
1 <template>
2 <div>
3 <div class="container">
4 <div class="container">
5 <div class="customPadding">
6 <div class="row">
7 <div class="col-sm">
8 <div class="customCard">
9 <h1
10 style="
11 font-size: 3rem;
12 text-align: center;
13 padding-bottom: 6rem;
14 "
15 >
16 Blog Post
17 </h1>
18 </div>
19 <p v-if="error">Something went wrong...</p>
20 <p v-if="loading">Loading...</p>
21 <div v-else>
22 <!-- {{ JSON.stringify(result.blogs.data) }} -->
23 <div v-for="blog in result.blogs.data" :key="blog.id">
24 <router-link class="nav-link" :to="'/blog/' + blog.id">
25 <div class="container">
26 <div class="customCard">
27 <div
28 class="card shadow-lg p-3 mb-5 bg-white rounded mr-5 ml-5"
29 >
30 <div class="card-head" style="padding: 4rem">
31 <h1 style="font-size: 6rem">
32 {{ blog.attributes.Title }}
33 <hr />
34 </h1>
35 </div>
36 <div class="card-body text-center">
37 <p style="line-height: 26pt">
38 {{ blog.attributes.Body }}
39 </p>
40 </div>
41 </div>
42 </div>
43 </div>
44 </router-link>
45 <br />
46 </div>
47 </div>
48 </div>
49 </div>
50 </div>
51 </div>
52 </div>
53 </div>
54 </template>
55 <script>
56 import gql from "graphql-tag";
57 import { useQuery } from "@vue/apollo-composable";
58 const POSTS_QUERY = gql`
59 query {
60 blogs {
61 data {
62 id
63 attributes {
64 Title
65 Body
66 }
67 }
68 }
69 }
70 `;
71 export default {
72 name: "App",
73 setup() {
74 const { result, loading, error } = useQuery(POSTS_QUERY);
75 return {
76 result,
77 loading,
78 error,
79 };
80 },
81 };
82 </script>
83 <style>
84 .customPadding {
85 margin-top: 12rem !important;
86 margin-bottom: 12rem !important;
87 /* padding: 4rem; */
88 }
89 </style>
First, we imported gql, used for making GraphQL query to Strapi endpoint. We stored the response data from the blog query : **[]**``array
.
Next, the data in this array was looped through and displayed in the HTML section of this component. We added a router link to fetch each post to the displayed post in the loop. This router link accepts the post id. The id is passed along to the single post component.
Your Vue app should now be able to fetch data from the Strapi GraphQL server. But running the application at this level results in an error because we have not created SingleBlog.vue component. In the next section will create BlogView.vue component.
We already added a router link to each displayed blog post to fetch a single post. In the views folder, create BlogView.vue and add the following code to the component.
1 <template>
2 <div>
3 <!-- single post blog CODE -->
4 <div class="container customPadding">
5 <div class="customPadding">
6 <div class="card card shadow-lg p-3 mb-5 bg-white rounded mr-5 ml-5">
7 <div v-if="blogId" class="container">
8 <div class="card-head" style="padding: 4rem">
9 <h1 style="font-size: 6rem">{{ blogTitle }}</h1>
10 <button
11 type="button"
12 class="btn btn-primary"
13 data-bs-toggle="modal"
14 data-bs-target="#exampleModal"
15 >
16 Edit
17 </button>
18 <button
19 type="button"
20 class="ms-2 btn btn-danger"
21 @click="processDelete(blogId)"
22 >
23 Delete
24 </button>
25 </div>
26 <hr />
27 <p style="line-height: 26pt">{{ blogContent }}</p>
28 </div>
29 </div>
30 </div>
31 </div>
32 <!-- single post blog CODE ends -->
33
34 <!-- MODAL CODE start -->
35
36 <!-- MODAL CODE ENDS -->
37 </div>
38 </template>
39
40 <script>
41 import gql from "graphql-tag";
42 import { ref, watch } from "vue";
43 import { useQuery, useMutation } from "@vue/apollo-composable";
44 import { useRoute, useRouter } from "vue-router";
45
46 export default {
47 setup() {
48 const route = useRoute();
49 const router = useRouter();
50 const blogId = ref(route.params.id);
51 const blogTitle = ref("");
52 const blogContent = ref("");
53
54 // FETCH POST
55 const { result } = useQuery(gql`
56 query {
57 blog(id: ${blogId.value}) {
58 data {
59 id
60 attributes {
61 Date
62 Title
63 Body
64 }
65 }
66 }
67 }
68 `);
69
70 // Update blogTitle and blogContent when result changes
71 watch(result, (newResult) => {
72 if (newResult && newResult.blog) {
73 const { Title, Body } = newResult.blog.data.attributes;
74 blogTitle.value = Title;
75 blogContent.value = Body;
76 }
77 });
78
79
80 return {
81 blogId,
82 blogTitle,
83 blogContent,
84 processDelete,
85 processEdit,
86 };
87 },
88 };
89 </script>
90
91 <style>
92 .customPadding {
93 margin-top: 12rem !important;
94 margin-bottom: 12rem !important;
95 /* padding: 4rem; */
96 }
97 </style>
Here, we modified our GraphQL query to fetch a single post by passing the post id along with the query.
For more on GraphQL queries with Vue, click here. If everything is done right, you should be able to click on a post from the home page and be redirected to a single page that will display the post's content.
Let's take a look at how we can implement the updating of our post content from within our Vue.js application. We need a form for this, so I added a bootstrap Modal with a form to the SingleBlog.vue component. Add the following code to the MODAL CODE section in the SingleBlog.vue template:
1 <!-- . . . -->
2 <!-- MODAL CODE start -->
3
4 <div class="container customPadding">
5 <div class="customPadding">
6 <div class="card card shadow-lg p-3 mb-5 bg-white rounded mr-5 ml-5">
7 <div v-if="blogId" class="container">
8 <div class="card-head" style="padding: 4rem">
9 <h1 style="font-size: 6rem">{{ blogTitle }}</h1>
10 <button
11 type="button"
12 class="btn btn-primary"
13 data-bs-toggle="modal"
14 data-bs-target="#exampleModal"
15 >
16 Edit
17 </button>
18 <button
19 type="button"
20 class="ms-2 btn btn-danger"
21 @click="processDelete(blogId)"
22 >
23 Delete
24 </button>
25 </div>
26 <hr />
27 <p style="line-height: 26pt">{{ blogContent }}</p>
28 </div>
29 </div>
30 </div>
31 </div>
32 <!-- MODAL CODE ENDS -->
Next, Let's add that function to the Methods object of our app like this.
<!-- . . . -->
// EDIT POST
const { mutate: editPost } = useMutation(gql
mutation updateBlog($id: ID!, $title: String!, $body: String!) {
updateBlog(id: $id, data: { Title: $title, Body: $body }) {
data {
id
}
}
}
);
const processEdit = (blogId, title, body) => {
let check = confirm(Confirm to edit post? ${blogId});
if (check) {
let edited = editPost({ id: Number(blogId), title: title, body: body });
if (edited) {
alert("Post updated. Refresh!");
}
}
};
The method looks similar to the Delete function implemented above except for the data we passed along in this case and the additional two variables added $Title: String! and $Body: String. Now you should be able to Edit
the post and save it as shown in the image below.
Let's take it even further by implementing the delete feature. We have already added a delete button for the delete functionality in the previous section. Now, the following method will be added to SingleBlog.vue to implement the functionality.
// DELETE POST
const { mutate: deletePost } = useMutation(gql`
mutation deleteBlog($id: ID!) {
deleteBlog(id: $id) {
data {
id
}
}
}
`);
const processDelete = (blogId) => {
let check = confirm(
`Are you sure you want to delete this post ${blogId}`
);
if (check) {
let deleted = deletePost({ id: blogId });
if (deleted) {
alert("Post deleted");
router.push("/")
}
}
};
When you click the button, it calls the deletePost() function. We used a JavaScript confirm function to confirm before making the GraphQL query to delete the post. Notice that variables in the GraphQl mutation are objects, not functions, as we had in the query earlier.
You've come a long way, so congrats. By now, you ought to know
Now you have the basics. It's good enough to start building real-world projects. Remember to share your experience with the rest of the Strapi community. You can find the Vue.js project code base on GitHub here.
Anumadu is a fullstack engineer, but also a technical writer specialized in Laravel, Vue.js, Nuxt.js and Node.js.