Full-stack web app with NextJS and Graphql

Published: 2020-08-31 21:24:20

# Full stack web app with NextJS and Graphql

When we talk about developing a front end application with ReactJS we can find 3 main options. `CRA` which focuses on building single-page web applications but having difficulty with CEO positioning. `Gatsby` which focuses on static site generation with great performance, with cool CEO and data-fetching. And then we have `NextJS`, in my opinion, the best way to write ReactJS web applications nowadays, server-side rendering with the option of making it client-side, cool built-in routing, 0 configuration philosophy, and since NextJS 9, this framework provides API routes which is a really easy way to provide back-end to our react app and which we are going to be using for this post. In this post, we are going to learn how we can implement a GraphQL API running over an API route.

## The basic NextJS app

As I mentioned before NextJS focuses on a 0 configuration philosophy we can easily configure it but to make it even easier let's just type `npx create-next-app todo-app` in our terminal to have it ready to use. Once the project is setup let's `cd todo-app` and type `yarn dev` to run the server and see that everything is running.

## The API Route

Cool!, We now have our nextjs web app up and running. Let's create a new file inside `pages/api/` called `graphql.js` and let's add the following code:
``` javascript
export default (req, res) => {
  res.statusCode = 200

  res.send("GRAPHQL")
}
```
And if we go to `localhost:3000/api/graphql`  we'll be able to see the text GRAPHQL written. Easy!. Let's now configure GraphQL!.

## GraphQL setup

### Install it
First, let's add a dependency called `apollo-server-micro` by writing `yarn add apollo-server-micro`

### Our Schema
The next thing we need to work with graphql is Writing our schema, which will define the Queries and Mutations we have and how the data is structured. For now, we want to have a query called `hello` which will return a string. So let's add the following to the top of our route.

``` javascript
import { ApolloServer, gql } from 'apollo-server-micro'

const schema = gql`
  type Query {
    hello: String!
  }
`;
```

### Resolvers
We have just written our schema, but now GraphQL needs the resolvers of our schema, which tells graphql where to fetch our data from. Below the schema let's add our resolvers.

``` javascript
const resolvers = {
  Query: {
    hello: (_parent, _args, _context) => "world!"
  }
}
```

### The server 
Now let's create our server with our schema and resolvers.
```javascript
const apolloServer = new ApolloServer({
  typeDefs: schema,
  resolvers,
  context: () => {
    return {}
  }
})
```
 Cool, with this instance we can access a handler, in charge of handling all the requests and responses and as we are actually working with NextJS we need to specify it that we don't need `bodyParser` in our requests.
 
 Let's remove the last `export default` and change it for the following
```javascript
const handler = apolloServer.createHandler({ path: "/api/graphql" });

export const config = {
  api: {
    bodyParser: false
  }
};

export default handler;
```

Okay, we have now a basic configuration of a GraphQL server, why don't we go to `localhost:3000/api/graphql` and see what we have now!

![graphql running image](https://i.imgur.com/Lmo7F5j.png)

and if we run the following
```graphql
query {
	hello
}
```
We will have our response from the resolver.  

### CORS
We need another thing to use this API from the front end, so let's add a new package by typing `yarn add micro-cors`, and let's add the following.

```javascript
import  Cors  from  "micro-cors";  

const cors =  Cors({ 
	allowMethods:  ["POST",  "OPTIONS"]
});  

// Here is how we connect our handler with cors.
export default cors(handler);
```

### Data from Postgres with Knex.
At some point, our app would need some sort of access to a database to persist some data. For this, we will need to set up some stuff so let's do it! First Let's add knex and Postgres with `yarn add knex pg` 

Now let's create a file called `knexfile.js` with our database configuration.
```javascript
module.exports = {
  development: {
    client: "postgresql",
    connection: "postgres://postgres@localhost:5432/todo",
    migrations: {
      tableName: "knex_migrations"
    }
  },
};
```

Next, let's create our first migration, which will tell Postgres how to create our tables, let's start by typing `yarn run knex migrate:make create_todo` and inside the folder `migrations` will have a new file generated, let's open it and add how we want our table to be created.

```javascript
exports.up = function(knex) {
  return knex.schema.createTable("todos", function(table) {
    table.increments("id");
    table.string("description", 255).notNullable();
    table.boolean("done").defaultTo(false).notNullable();
  });
};

exports.down = function(knex) {
  return knex.schema.dropTable("todos");
};
```

And let's build our table!. Run `yarn run knex migrate:up` 

Now we need to create a constant which will help us manage the database inside our code. Let's open `/pages/api/graphql.js` and add the following.
```javascript
import knex from "knex";

const db = knex({
  client: "pg",
  connection: "postgres://postgres@localhost:5432/todo"
});
```

### Updating our schema
Cool, we can now work. Why don't we change our schema and resolvers?
```js
const schema = gql`
  type Query {
    todos: [Todo]!
    todo(id: ID!): Todo
  }

  type Todo {
    id: ID!
    description: String!
    done: Boolean!
  }
`;

const resolvers = {
  Query: {
    todos: (_parent, _args, _context) => {
      return db
        .select("*")
        .from("todos")
        .orderBy("id")
    },
    todo: (_parent, { id }, _context) => {
      return db
        .select("*")
        .from("todos")
        .where({ id })
        .first()
    }
  }
}
```

And if we now go to `localhost:3000/api/graphql` we'll finally be able to fetch and play with our data!

![empty query image](https://i.imgur.com/FBiig6g.png)

But wait a minute... How are we going to create data?
Well...
Let's go and see how we can add a `Mutation` which will help us create data inside our database!

### Creating data
First, we have to add a new type inside our schema. We first need to specify the name of the mutation in this case `createTodo` and then inside the parenthesis the names and types of values that we will receive, at the end, we need to specify what our mutation will return, in this case, a `Todo` type.
```js
const schema = gql`
  ...
  
  type Mutation {
    createTodo(description: String!, done: Boolean): Todo
    completeTodo(id: ID!): Todo
  }
`;
```

Now inside our `resolvers` object let's add the new `Mutation` key and the `createTodo`
```js
const resolvers = {
  ...

  Mutation: {
    createTodo: async (_, { description, done }, _c) => {
      return (await db("todos").insert({ description, done }).returning("*"))[0]
    },
    completeTodo: async (_, { id }, _c) => {
      return (await db("todos").select("*").where({ id }).update({ done: true }).returning("*"))[0];
    }
  }
}
```

And with this, now we can create and complete todos in our database

![create todo image](https://i.imgur.com/YCvJP8n.png)
![complete todo image](https://i.imgur.com/31SOBFY.png)

Cool, but what about the Front-end?

## The client

Until now we have been building the server-side of our application by integrating graphql in an API route, why don't we integrate the client side of our app?

### Dependencies
Let's start by adding two dependencies we need to connect to GraphQL
 `yarn add @apollo/react-hooks apollo-boost`

### Provider
First let's setup the Apollo Client of our app. for this let's open `pages/_app.js` and add the following:

```jsx
import '../styles/globals.css'
import { ApolloProvider } from '@apollo/react-hooks';
import ApolloClient, { gql } from 'apollo-boost';


function MyApp({ Component, pageProps }) {
  const client = new ApolloClient({
    uri: "http://localhost:3000/api/graphql"
  })

  return (
    <ApolloProvider client={client}>
      <Component {...pageProps} />
    </ApolloProvider>
  )
}

export default MyApp
```

### Query

Now let's open our `pages/index.js` and import what we are going to need

```js
import React, { useState } from 'react';
import { useQuery, useMutation } from "@apollo/react-hooks";
import { gql } from 'apollo-boost';
```

Now, we first need to declare our GraphQL query, exactly as we would do in the GraphQL Playground
```js
const GET_TODOS = gql`
  query {
    todos {
      id
      description
      done
    }
  }
`
```

Now inside our component, we are going to use the query and map through them to render them in the app

```jsx
export default function Home() {
  const { loading, error, data, refetch } = useQuery(GET_TODOS);

  if(loading) return <p>Loading...</p>
  if(error) return <p>ERROR :(</p>

  return (
    <div>
      <h1>My TODO list</h1>
      
      {
        data.todos.map((todo) => (
          <div key={todo.id}>
            {todo.description}
            <button
              disabled={todo.done}
            >
              {todo.done ? "Done" : "Complete"}
            </button>
          </div>
        ))
      }

    </div>
  )
}
```

Cool, now we should be seeing our todos on the browser. Now let's add a way to create todos. Let's start by adding the `createTodo` mutation
```js
const CREATE_TODO = gql`
  mutation CreateTodo($description: String!) {
    createTodo(description: $description) {
      id
      description
      done
    }
  }
`
```

Now inside our component, we add some state management, our mutation, and our form to perform the mutation to end up like this:
```jsx
export default function Home() {
  ...
  const [todo, setTodo] = useState("");
  const [createTodo] = useMutation(CREATE_TODO);

  const saveTodo = async (e) => {
    e.preventDefault();
    await createTodo({variables: { description: todo }});
    refetch();
    setTodo("")
  }
 
  ...

  return (
    <div>
      <h1>My TODO list</h1>

      <form onSubmit={saveTodo}>
        <label>
          New todo
          <input onChange={e => setTodo(e.target.value)} value={todo} />
        </label>
        <button type="submit">Save</button>
      </form>

      ...
      
    </div>
  )
}
```

### Completing todos

Pretty straightforward!, Why don't we add our `completeTodo` mutation and add functionality to our buttons?
Here we have our mutation declaration:
```js
const COMPLETE_TODO = gql`
  mutation CompleteTodo($id: ID!) {
    completeTodo(id: $id) {
      id
    }
  }
`
```

And inside our component we have:

```jsx
export default function Home() {
  const [todo, setTodo] = useState("");
  const { loading, error, data, refetch } = useQuery(GET_TODOS);
  const [createTodo] = useMutation(CREATE_TODO);
  const [completeTodo] = useMutation(COMPLETE_TODO)

  const saveTodo = async (e) => {
    e.preventDefault();
    await createTodo({variables: { description: todo }});
    refetch();
    setTodo("")
  }

  const onComplete = async (id) => {
    await completeTodo({variables: { id }});
    refetch();
  }


  if(loading) return <p>Loading...</p>
  if(error) return <p>ERROR :(</p>

  return (
    <div>
      <h1>My TODO list</h1>

      <form onSubmit={saveTodo}>
        <label>
          New todo
          <input onChange={e => setTodo(e.target.value)} value={todo} />
        </label>
        <button type="submit">Save</button>
      </form>

      {
        data.todos.map((todo) => (
          <div key={todo.id}>
            {todo.description}
            <button
              disabled={todo.done}
              onClick={() => onComplete(todo.id)}
            >
              {todo.done ? "Done" : "Complete"}
            </button>
          </div>
        ))
      }

    </div>
  )
}
```

And if now we go to our browser we can see our app working!

![app running](https://media0.giphy.com/media/TdG9m7QLteD7bFC7Sx/giphy.gif)

## Conclusion

GraphQL is a technology that has been growing a lot in the last couple of years and so has NextJS. And now that we can have API routes in our NextJS app we can integrate them to build a delightful stack and be able to have a full-stack web application as a monolith but which can even run serverless 🤔?

<span>Photo by <a href="https://unsplash.com/@alinnnaaaa?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Alina Grubnyak</a> on <a href="https://unsplash.com/s/photos/network?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></span>