React TodoMVC with Apollo - Server
Tags: react, graphql, apollo, tutorial
Published: 2020-08-30 10:19:42

![Photo by Md Mahdi on Unsplash](https://dev-to-uploads.s3.amazonaws.com/i/ca2xwrplagd3orhyzm1o.jpg)
In this tutorial I will share how to build a simple TodoMVC with Apollo server and Apollo client 3. We will cover the basics of apollo server and client by build a React TodoMVC app.
This is part one of the tutorial - build backend with Apollo server.

- 1. React TodoMVC with Apollo - Server
- 2. React TodoMVC with Apollo - Client (WIP)
- 3. React TodoMVC with Apollo - Test (WIP)
- 4. React TodoMVC with Apollo - Deployment (WIP)

# Prerequisite

If you are new to React or Apollo/Graphql, make sure you go through links below first to get a gist.

- React
- Graphql
  https://graphql.org/
- Apollo

Without further ado lets get started!

# installation

Create our project folder:

```
mkdir todomvc-apollo-server
cd todomvc-apollo-server

```

First, create a `package.json` contains libraries we need for our project:

```json
{
  "name": "todomvc-apollo-server",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "nodemon --exec babel-node src/index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/cli": "^7.10.5",
    "@babel/core": "^7.11.4",
    "@babel/node": "^7.10.5",
    "@babel/preset-env": "^7.11.0",
    "nodemon": "^2.0.4"
  },
  "dependencies": {
    "apollo-server": "^2.17.0",
    "graphql": "^15.3.0"
  }
}
```

## Install dependencies:

```
npm i
```

## Config babel

To use new javascript features we need to config babel by creating a `.babelrc` with content:

```json
{
  "presets": ["@babel/preset-env"]
}
```

You can use [npm-check-updates](https://www.npmjs.com/package/npm-check-updates) to update to latest versions.

# Implementation

Next stop lets implement a basic apollo server with an api that returns our todo list.

## 1 - First Graphql Query

### schema

A schema is where we define our types, query, mutation and subscription. In our tutorial, we have a `TODO` type, and a `todos` Query. That means as a client when I query todos, I expect a array of TODO as response. Exclamation mark means the value can not be null (I.e. Your resolver should not return null for this type). Read more on : https://graphql.org/learn/schema/#lists-and-non-null.

Create `typeDefs.js` with following content.

```javascript
const { gql } = require("apollo-server");

export const typeDefs = gql`
  type Query {
    todos: [TODO!]!
  }
  type TODO {
    id: ID!
    text: String!
    completed: Boolean!
  }
`;
```

### resolvers

Create `resolvers.js` with following content.

Simply put, resolver defines what we should do for a query/mutation/subscription. In this case, we simply returns an array of TODO for Query `todos`.

```javascript
export const resolvers = {
  Query: {
    todos: () => [
      {
        id: "0",
        text: "Buy milk",
        completed: false,
      },
      {
        id: "1",
        text: "Sing a song",
        completed: true,
      },
    ],
  },
};
```

### Index.js

Create `index.js`, where we start our apollo server.

```javascript
import { ApolloServer } from "apollo-server";
import { typeDefs } from "./typeDefs";
import { resolvers } from "./resolvers";

const server = new ApolloServer({ typeDefs, resolvers });

server.listen().then(({ url }) => {
  console.log(`🚀  Server ready at ${url}`);
});
```

Now we finished our basic apollo server. run `npm start` then open `http://localhost:4000`, you should be able to see apollo playground running! 🚀

Try the query below you should be able to see apollo return our todo list at right side! 🐸

```
query todos {
  todos {
    id
    text
    completed
  }
}

```

> You can find code we had so far in here.  
> https://github.com/bluedusk/todomvc-apollo/tree/server/1-basic-apollo-server

## 2 - Mutations

### Mock datasource

Before we start add mutations to our server. We need to create a mock datasource to provide basic todo CRUD functions, create a `datasource.js` as blow:

```typescript
/**
 * A mock datasource providing todo CRUD functionalities
 */
export class Todos {
  constructor() {
    this.todos = [
      {
        id: "0",
        text: "Buy milk",
        completed: false,
      },
      {
        id: "1",
        text: "Sing a song",
        completed: true,
      },
    ];
  }

  getTodos() {
    return this.todos;
  }

  setTodos(todos) {
    this.todos = todos;
  }

  addTodo(todoText) {
    const todo = {
      id: String(this.getTodos().length + 1),
      text: todoText,
      completed: false,
    };
    this.todos.push(todo);
    return todo;
  }

  deleteTodo(id) {
    const todo = this.todos.find((todo) => todo.id === id);
    if (todo) {
      this.todos = this.todos.filter((todo) => todo.id !== id);
    }
    return todo;
  }

  updateTodoById(id, text) {
    let result;
    this.todos.forEach((todo) => {
      if (todo.id === id) {
        // update text or completed
        if (text) {
          todo.text = text;
        } else {
          todo.completed = !todo.completed;
        }
        result = todo;
      }
    });

    return result;
  }
  deleteAll() {
    this.todos = [];
  }
  deleteCompleted() {
    this.todos = [...this.todos].filter(({ completed }) => !completed);
  }
  completeAll() {
    this.todos = [...this.todos].map((todo) => {
      return {
        ...todo,
        completed: true,
      };
    });
  }
}
```

### Update typeDefs

Add following mutation to `typeDefs.js`

```typescript
  type Mutation {
    addTodo(text: String!): TODO!
    updateTodo(id: ID!, text: String): TODO!
    deleteTodo(id: ID!): TODO
    completeAll: Boolean
    deleteCompleted: Boolean
  }
```

### Update resolvers

Replace `resolvers.js` with following updates. Notice that how we get arguments and context in resolver functions.

```typescript
export const resolvers = {
  Query: {
    todos: (parent, args, { Todos }) => {
      return Todos.getTodos();
    },
  },
  Mutation: {
    addTodo: (parent, { text }, { Todos }) => {
      const result = Todos.addTodo(text);
      return result;
    },
    deleteTodo: (parent, { id }, { Todos }) => {
      const result = Todos.deleteTodo(id);
      return result;
    },
    updateTodo: (parent, { id, text }, { Todos }) => {
      const result = Todos.updateTodoById(id, text);
      return result;
    },
    deleteCompleted: (_, __, { Todos }) => {
      Todos.deleteCompleted();
      return true;
    },
    completeAll: (_, __, { Todos }) => {
      Todos.completeAll();
      return true;
    },
  },
};
```

### Update index.js

Replace `index.js` with following updates. Notice that we _injected_ Todos to context so that it can be used in each resolver method in last section. In our example we want todos object shared across multiple queries, so context is an object not a function such that it will not be triggered for each request. In reality, we might want that for each request we want new context, we can set context to be a funcion: ()=> { /\*\*\*/}

> More on context: https://www.apollographql.com/docs/apollo-server/data/resolvers/#the-context-argument

```typescript
import { ApolloServer } from "apollo-server";
import { typeDefs } from "./typeDefs";
import { resolvers } from "./resolvers";
import { Todos } from "./datasource";

const server = new ApolloServer({
  typeDefs,
  resolvers,
  // context: Where we "inject" our fake datasource
  context: {
    Todos: new Todos(),
  },
  // plugins(optional): A small plugin to print log when server receives request
  // More on plugins: https://www.apollographql.com/docs/apollo-server/integrations/plugins/
  plugins: [
    {
      requestDidStart(requestContext) {
        console.log(
          `[${new Date().toISOString()}] - Graphql operationName:  ${
            requestContext.request.operationName
          }`
        );
      },
    },
  ],
  // capture errors
  formatError: (err) => {
    console.log(err);
  },
});

// The `listen` method launches a web server at localhost:4000.
server.listen().then(({ url }) => {
  console.log(`🚀  Server ready at ${url}`);
});
```

To avoid playground IntrospectionQuery spam your console, change playground setting as blow:

```
  "schema.polling.enable": false,
```

Thats it! make sure your server is running and check `schema.test.graphql` in the repo, you can now try some mutation in playground!

> You can find code we had so far in here.  
> https://github.com/bluedusk/todomvc-apollo/tree/server/2-mutations

## Subscription

Our next mission is to play around with graphql subscription.
Subscription comes in handy when we want to receive updates via server push, rather than constantly polling server from client. (Note that polling might be good enough for some usecases, providing Apollo supports dynamic polling which allows you to start/stop and set polling interval dynamiclly. https://www.apollographql.com/docs/react/data/queries/#usequery-api)

> Unlike queries, subscriptions maintain an active connection to your GraphQL server (most commonly via WebSocket). This enables your server to push updates to the subscription's result over time.
> https://www.apollographql.com/docs/react/data/subscriptions/

### schema

Add submission type to `typeDefs.js`, like Query and Mutation, Subscription is another root type.

```typescript
  type Subscription {
    todos: [TODO!]!
  }
```

### resolver

Replace `resolvers.js` with content below:

```typescript
import { PubSub } from "apollo-server-express";

const pubsub = new PubSub();
const TODO_CHANGED = "TODO_CHANGED";

const doPublish = (todos) => {
  pubsub.publish(TODO_CHANGED, { todos });
};

export const resolvers = {
  Query: {
    todos: (parent, args, { Todos }) => {
      return Todos.getTodos();
    },
  },
  Mutation: {
    addTodo: (parent, { text }, { Todos }) => {
      const result = Todos.addTodo(text);
      doPublish(Todos.getTodos());
      return result;
    },
    deleteTodo: (parent, { id }, { Todos }) => {
      const result = Todos.deleteTodo(id);
      doPublish(Todos.getTodos());
      return result;
    },
    updateTodo: (parent, { id, text }, { Todos }) => {
      const result = Todos.updateTodoById(id, text);
      doPublish(Todos.getTodos());
      return result;
    },
    deleteCompleted: (_, __, { Todos }) => {
      Todos.deleteCompleted();
      doPublish(Todos.getTodos());
      return true;
    },
    completeAll: (_, __, { Todos }) => {
      Todos.completeAll();
      doPublish(Todos.getTodos());
      return true;
    },
  },
  Subscription: {
    todos: {
      subscribe: () => {
        return pubsub.asyncIterator([TODO_CHANGED]);
      },
    },
  },
};
```

Notice that in our subscription resolver, we are listening event `TODO_CHANGED`:

```typescript
  subscribe: () => {
    return pubsub.asyncIterator([TODO_CHANGED]);
  },

```

We publish this event in our mutations such as add/delete/update todo:

```typescript
pubsub.publish(TODO_CHANGED, { todos });
```

To test subscription, we open two playground (http://localhost:4000), in first one we start subscription with :

```typescript
subscription sub_todos {
  todos {
    id
    text
    completed
  }
}
```

You can see it start listening. In second playground we add one todo:

```
mutation addTodo {
  addTodo(text: "another todo") {
    id
    text
    completed
  }
}

```

After addTodo we can see in first playground we received the event and updated todo list！ 🌟

> You can find code we had so far in here.  
> https://github.com/bluedusk/todomvc-apollo/tree/server/3-subscriptions

# End

That it for apollo server part of this tutorial. In next part we will build our todoMvc react client with apollo client.  Thanks for reading!