Learn Fullstack Web Development with React and GraphQL by building a Sticky Note app - Part two
Tags: graphql, apolloserver, typescript, javascript
Published: 2023-02-20 17:46:58

---
title: Learn Fullstack Web Development with React and GraphQL by building a Sticky Note app - Part two
published: true
date: 2023-01-09 16:44:54 UTC
tags: GraphQL, ApolloServer, TypeScript, JavaScript
canonical_url: https://kingdavvid.hashnode.dev/learn-fullstack-web-development-with-react-and-graphql-by-building-a-sticky-note-app-part-two
---

## Introduction

Hello again! This article is the second part of a series titled, " **Learn Fullstack Web Development with React and GraphQL by building a Sticky Note app**".

In the first part of the series, we built the frontend aspect of this app using React and TypeScript.

In this section, we will to build the backend using GraphQL, Apollo Server 4, Nexus, ExpressJS and MongoDB.

## System Requirements

Make sure you already have the following installed:

- [Nodejs](nodejs.org) v16 or later (This project uses v18.12.0)

- [VScode](https://code.visualstudio.com/)

# Prerequisites

This tutorial assumes knowledge of the following:

- **JavaScript** : you are already familiar with ES6 features such as the fat arrow function and object destructuring

- **TypeScript:** You have a little experience with TypeScript or at least know what TypeScript is and why it's being used in place of JavaScript.

- **NPM:** You have used the Node Package Manager to install a library and perhaps also to run your code or start a server.

# Tools

We'll be using the following tools to build our GraphQL server.

- **Nodejs**

- **Mongoose**

- **MongoDB**

- **GraphQL**

- **Expressjs**

- **Apollo Server**

- **Nexus**

- **TypeScript**

# Initializing the project

Create a new folder called `sticky-notes-backend` or you can give it any name you prefer. This folder will be used to store all the server files. Open your terminal, and navigate to the newly created folder, then run the following command:

```
npm init -y

```

Running the above command will initialize your project with **NPM** and create `package.json` file at the root directory of your project.

# Installing TypeScript and `ts-node-dev`

**TypeScript** is a strongly typed programming language that extends the capabilities of JavaScript with additional features and syntax. Since we are using it for our project, we need to install and configure it first. We also need to install the [ts-node-dev](https://www.npmjs.com/package/ts-node-dev) library.

The `ts-node-dev` package will be used for two things:

1. to transpile our TypeScript code to JavaScript before executing it

2. to automatically restart the GraphQL dev server whenever a change is made to the code.

Run the following command on your terminal to install them as dev dependencies:

```
npm install --save-dev typescript@^4.3.5 ts-node-dev@^1.1.8

```

After the installations, we'll create a new configuration file for TypeScript.

In the root directory of your backend project, create a new file called `tsconfig.json`.

Open up the `tsconfig.json` with your editor, then copy and paste the following configurations into it:

```
{
  "compilerOptions": {
    "target": "ES2018",
    "module": "commonjs",
    "lib": [
      "esnext"
    ],
    "strict": true,
    "rootDir": ".",
    "outDir": "dist",
    "sourceMap": true,
    "esModuleInterop": true
  }
}

```

We use those options to set the TypeScript Compiler's behavious. For example we used the `target` option too specify the ECMAScript target version that the code should be compiled to, which is "ES2018" and setting the `strict` option to `true` will enforce the compiler to use stricter type checking rules, which can help in catching potential type errors in your code.

# Setting up the Server

## Installing the necessary Libraries

To install all of the necessary libraries for this project, run the following command in the terminal:

```
npm install @apollo/server express graphql nexus cors cookie-parser mongoose dotenv jsonwebtoken cookie-parser cors validator bcrypt

```

Once the installation is finished, use the following command to install the type definitions for the libraries:

```
npm i --save-dev @types/node @types/jsonwebtoken @types/cors @types/cookie-parser @types/validator @types/bcrypt

```

Here is a summary of the installed libraries:

- **@apollo/server:** This is the core engine for the Apollo GraphQL server. It provides a set of tools for building a GraphQL server in Node.js.

- **Express:** It is a backend web application framework for building RESTful APIs with Node.js

- **GraphQL** : is the library that implements the core GraphQL parsing and execution algorithms.

- **Nexus** : A code-first, type-safe GraphQL schema construction library for JavaScript/TypeScript. We'll talk more on this and why we are using it later.

- **Cookie-parser** : A middleware that parses cookies attached to the client request object.

- **Mongoose:** A Node.js-based Object Data Modeling (ODM) library for MongoDB.

- **dotenv:** A Node.js library that allows you to load environment variables from a `.env` file into your project.

- **Jsonwebtoken** : This library will be used to generate a unique token for an authenticated user.

- **Cors** : is an express middleware that will be used to enable [Cross-origin resource sharing](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) on our app.

- **Validator** : will be used to verify the user's details before saving them into the database.

- **bcrypt** : This library will be used to encrypt the user's password before saving them into the database.

# Code First VS Schema First Approach in GraphQL

There are two different ways to implement GraphQL API, they are:

- Schema First Approach and,

- Code First Approach

### Schema First

With this approach, the schema is defined first using the Schema Definition Language (SDL) before implementing the resolver functions. This method is also referred to as SDL-first Approach.

### Code First

The Code first approach allows developers to use their programming language of choice (TypeScript in our case) to define the schema, which can be more expressive and easier to read and maintain than Schema Definition Language.

We will be using the code-first approach in this project.

# What is Nexus?

[Nexus](https://nexusjs.org/) is a code-first, type-safe GraphQL schema construction library for JavaScript and TypeScript. It is a powerful tool that can be used to build and deploy GraphQL servers in JavaScript/TypeScript more efficiently.

## Setting up Nexus

At the root directory of the project, create a new folder called `src`. Inside the `src` directory, create two new TypeScript files named `schema.ts` and `index.ts.`

The `schema.ts` file will be used for generating our GraphQL schema with Nexus. The `index.ts` file will be used to setup our GraphQL web server with Apollo server and express.

Inside `src/schema.ts` file, type in the following code:

```
import { makeSchema } from 'nexus'
import { join } from 'path'

export const schema = makeSchema({
  types: [],
  outputs: {
    schema: join(process.cwd(), "schema.graphql"),
    typegen: join(process.cwd(), "nexus-typegen.ts"), 
  },
})

```

- The `types:[]` will contain GraphQL types that will be used to generate our GraphQL schema, it is empty for now. Soon, we will create the types and replace the empty array with them.

- The `schema: join(process.cwd(), "schema.graphql")` will configure Nexus to generate a `schema.graphql` file at the root directory of our project where nexus will automatically write the SDL version of our GraphQL schema.

- The `typegen: join(process.cwd(), "nexus-typegen.ts")` will set up nexus to generate `nexus-typegen.ts` file at the root directory of our project where nexus should write the generated TypeScript definition types derived from our schema.

Let's run the following command in the terminal and see what happens next:

```
npx ts-node --transpile-only src/schema

```

After executing the script above, Nexus will automatically generate two new files in the root directory of your project, they are: `schema.graphql` and `nexus-typegen.ts`.

The `schema.graphql` file has a type named Query, with a single field called `ok` because we did not provide Nexus with more information about the types we wanted in the schema, this was constructed as a default schema.

Let's add a `generate` script to the package.json file:

```
"generate": "ts-node --transpile-only src/schema.ts"

```

Any time you run `npn run generate` on your terminal, it will regenerate your schema by updating the `schema.graphql` and `nexus-typegen.ts` file if there are any changes in your Nexus code.

## Setting up the Apollo GraphQL Server with Express.js

There are different ways to create an `Apollo Server`. This tutorial uses Apollo Server as a middleware with `express.js`, there's also a tutorial that uses [the Apollo Server's](https://www.apollographql.com/blog/apollo-client/next-js/next-js-getting-started/) `startStandaloneServer` on Apollo's website.

In this situation, it is not recommended to use the `startStandaloneServer` function because we want to be able to customize the CORS' behavior and run the `cookieParser` middleware before processing the GraphQL requests.

Open up the `src/index.ts` file we created earlier, and add the following codes to it:

```
import express, { Response } from "express";
import { expressMiddleware } from '@apollo/server/express4';
import { ApolloServer } from '@apollo/server';

import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
import http from 'http';
import { schema } from "./schema";
import cors from 'cors';
import cookieParser from "cookie-parser";
import { json } from 'body-parser';

require('dotenv').config()

async function startApolloServer() {
  const app = express();

   const corsOptions = {
    origin: process.env.FRONTEND_URL!,
    credentials: true,
  };

  const httpServer = http.createServer(app);

  const server = new ApolloServer({
    schema,
    plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
  });

  await server.start();
  app.use(
    '/graphql',
    cors(corsOptions),
    json(),
    cookieParser(),
    expressMiddleware(server, {
      context: async ({ res }:{ res:Response })=> {
        return {
          res
        }
      }
    })
  );
  await new Promise<void>((resolve) => httpServer.listen({ port: 4000 }, resolve));
  console.log(`🚀 Server ready at http://localhost:4000/graphql`);
}

startApolloServer();

```

## Understanding what is going on in the server file:

We created a function called `startApolloServer`. This is the function that will be called anytime we want to start the server. Inside the function, we created an instance of `express()` called `app`.

After that, we created a new object called `corsOptions`. Inside the `corsOptions` we set the `origin` to our front-end URL meaning only the requests from the front-end URL will be allowed. This will protect our GraphQL server from being accessed from an unknown origin.

The `credentials: true` option will set the [Access-Control-Allow-Credentials](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials#:~:text=The%20Access%2DControl%2DAllow%2D,a%20request's%20credentials%20mode%20(%20Request.) option to `true`. This will enable the browser to read the cookies that will be sent to it from the server.

The `const httpServer = http.createServer(app);` creates a new HTTP server and passes Express.js (`app)` as a callback to handle HTTP requests.

The `app.use(cookieParser());` is used to register `cookieParser` as an express `middleware` function. Middleware functions are functions that have access to the `request` object (req), the `response` object (res), and the `next` middleware function in the applications request-response cycle.

The `cookieParser()` function will be used to parse the `cookies` sent from the user's browser. More about `cookies` and why they are being used will be discussed in the authentication part of this tutorial.

We used the following snippets:

```
const server = new ApolloServer({
    schema,
    plugins: [ApolloServerPluginDrainHttpServer({ httpServer })]
  });

```

to create an instance of ApolloServer and passed the `schema` that we created earlier, and the `ApolloServerPluginDrainHttpServer` object to it. We used the `ApolloServerPluginDrainHttpServer` plugin to ensure the server gracefully shuts down.

We used the following snippets:

```
  app.use(
    cors(corsOptions),
    json(),
    cookieParser(),
       expressMiddleware(server, {
      context: async ({ res }:{ res:Response })=> {
        return {
          res
        }
      }
    }),
    '/graphql'
  )

```

to register `cors()`, `json()`, `cookieParser()`, `expressMiddleware()` as an express `middleware` function.

The `expressMiddleware()` function enables us to attach the Apollo Server to the Express server as a middleware function. It also has a second optional argument which is an object that contains a `context` function. The `context` function will return an object (`res` in this case and `req` later in this tutorial) which will be shared by all the `resolvers` during the executions of an operation. We will see more of `context` and `resolvers` in action later in this tutorial.

The `/graphql` registered to the middleware will set the `/graphql` URL path as the default part of our server. This will enable us to access our server only on the following path: `http://localhost:4000/graphql`

In the package.json file, add the following `dev` script to it.

```
"dev": "ts-node-dev --transpile-only --no-notify --exit-child src/index.ts"

```

Now we can run the following command to start the backend development server:

```
npm run dev

```

Once the server has started running, we should see something like this on the `CLI`:

![Screenshot_5.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1664963021461/vfB63S8x1.png)

When you visit `http://localhost:4000/graphql` on your browser:

![Screenshot_7.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1667045846113/4IQuSfeir.png)

You'll notice that Apollo no longer uses GraphQL Playground and is now using [Apollo Sandbox](https://www.apollographql.com/docs/graphos/explorer/sandbox/), as the standard landing page in Apollo Server 4.

Additionally, you need an internet connection to view [http://localhost:4000/graphql](http://localhost:4000/graphql) on your browser.

# Setting up the Database

We'll be using MongoDB Atlass for this tutorial. MongoDB Atlas is a fast, easy, and free way to get started with MongoDB. Signup on the [MongoDB website](https://www.mongodb.com/cloud/atlas/register) to get your database URI string.

The URI string will be used to connect to your database. It looks like this: `mongodb+srv://<username>:<password>@<cluster-name>yocfhhk.mongodb.net/?retryWrites=true&w=majority`.

You can follow this guide on [freecodecamp's website](https://www.freecodecamp.org/news/get-started-with-mongodb-atlas/) on how to set up your MongoDB Atlas account and get your MongoDB database URI string.

## Creating the environment variables

At the root directory of your project, create a `.env` file. Open it in your editor and add your MongoDB URI string to it like this:

```
DATABASE_URL='Your-MongoDB-URL-String-goes-here'

```

That's all for now, later in the article, we'll be adding our `JWT_SECRET` and `FRONTEND_URL` to the `.env` file.

## Connecting to the Database

Open up the server file in `src/index.ts` and import the mongoose object.

```
import mongoose from 'mongoose';

```

After that, we will create a new function called `connectToMongoDB`.

```
async function connectToMongoDB() {
  await mongoose.connect(process.env.DATABASE_URL!);
  console.log("Connected to Mongo atlas successfully...");
}

```

Then call the `connectToMongoDB()` function inside the `startApolloServer()` like so:

```
  console.log(`🚀 Server ready at http://localhost:4000/graphql`);
  connectToMongoDB().catch(err => console.log(err));
}

```

Run the following command on your terminal to start your server

```
npm run dev

```

When the server starts, it should automatically establish a connection to your database URI if everything goes according to plan.

An internet connection is required to connect to the MongoDB Atlas.

![Screenshot_8.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1667122674980/ik0yngnya.png)

## Creating the models

In this part, we will define the structure of our database for the users and their sticky notes with `Mongoose`.

[Mongoose](https://mongoosejs.com/) is an Object Data Modeling (ODM) library for MongoDB and Node.js. It provides a simple and straightforward way to interact with MongoDB databases from a Node.js application, allowing you to work with your data as native JavaScript objects.

### Defining the `User` Model

Create a new directory in the `src` folder named: `models`. Open the `models` folder and create a new file named: `user.ts` inside it.

Open up the `user.ts` file and add the following code to it:

```
import { Schema, model } from 'mongoose';

interface IUser {
  id:string;
  fullname: string;
  username: string;
  email: string;
  password: string;
}

const userSchema = new Schema<IUser>({
  email: {
    type: String,
    required: true,
    unique: true,
  },
  username: {
    type: String,
    unique: true,
    required: true,
    trim: true,
  },
  password: {
    type: String,
    required: true,
  },
  fullname: {
    type: String,
    required: true,
    trim: true,
  },
});

const User = model<IUser>('User', userSchema);

export default User;

```

First, we created an interface called `IUser` that will represent a user in our database, then we defined a `userSchema` that conformed with the `IUser` interface before generating a `User` model from the `userSchema` .

The `User` model can then be used to create, read, update, and delete documents in the `users` collection in the database.

### Defining The Notes Model

Create another file named `note.ts` in the `src/models` directory, and add the following code:

```
import { Schema, model, Document } from 'mongoose';

enum Theme {
    Yellow = "yellow",
    Green = "green",
    Pink = "pink",
    Purple = "purple",
    Blue = "blue",
    Gray = "gray",
    Charcoal = "charcoal"
}

interface INote {
    content: string;
    color: Theme;
    userId: string;
}

const noteSchema = new Schema<INote>({
    content: {
        type: String,
        required: true
    },
    color: {
        type: String,
        enum: ['yellow', 'green', 'pink', 'purple', 'blue', 'gray', 'charcoal'],
        default: Theme.Yellow
    },
    userId: {
        type: String, required: true
    },
});

const Note = model<INote>('Note', noteSchema);
export default Note;

```

We used the TypeScript's enum type to define all the colors that are available for use on the note app, and created an interface for the note called `INote`.

Then we defined a `noteSchema` that corresponds with the `INote` interface before generating a Note model from the `noteSchema`.

After that, we exported the model for later use. We also used the Mongoose enum type to specify the accepted colors (`enum: ['yellow', 'green', 'pink', 'purple', 'blue', 'gray', 'charcoal'],`), any other color than those listed will be rejected.

# Creating The User Types

Create a new folder in the `src` directory called `graphql`. Inside the `graphql` directory, create a new TypeScript file called `User.ts` and open it up in your editor, then import the following objects into it:

```
import bcrypt from "bcrypt";
import jsonwebtoken from "jsonwebtoken";
import {GraphQLError } from "graphql"
import validator from 'validator';
import { extendType, objectType, inputObjectType, nonNull } from "nexus";
import userModel from "../models/user";

```

Before we proceed, let's quickly go through the imports:

- The `bcrypts` object will be used to encrypt the user's password before saving it to the database.

- `jsonwebtoken` will be used to generate a safe token that will be used to authenticate a user after successful registration or login.

- `GraphQLError` will be used to specify HTTP status code when throwing a GraphQL error.

- `validator` will be used to validate user inputs before saving them into the database.

- `extendType` will be used to extend the GraphQL root types in this project.

- `objectType` will be used to define our object types.

- `inputObjectType` will be used to define the object type that will be passed as an input value.

- the `nonNull` function will be used to prevent the return value of a type from being empty

## Defining the types for a User

Remember we are using the code-first approach to build our GraphQL server, which means we have to write our schemas in TypeScript then later, we'll use Nexus to generate the Schema Definition Language from our code. Let's define the following types for a user:

- `User`

- `AuthPayLoad`

- `SignupUserInput`

- `SignInUserInput`

Add the following snippets below the imports to create the `User` type:

```
 const User = objectType({
    name: "User", 
    definition(t) {
      t.nonNull.string("id");
      t.nonNull.string("email");
      t.nonNull.string("username");
      t.nonNull.string("password");
      t.nonNull.string("fullname");
    },
  });

```

We used the Nexus `objectType` function to create a new type called `User`. The `User` type will be used to retrieve the user's data from the server. Five fields are defined in the `User` object types, they are: `id`, `email`, `username`, `password` and `fullname`. Each field is defined as a non-nullable `string` type.

### Defining the `AuthPayLoad` Type

Let's create another object type called `AuthPayLoad`. This type will be used to retrieve a user's token and data after successful registration or login.

Add the following code below the `User` object type:

```
   const AuthPayLoad = objectType({
    name: "AuthPayLoad",
    definition(t) {
      t.nonNull.string("token");
      t.nonNull.field("user", { type: User });
    },
  });

```

The `AuthPayLoad` type contains two fields: `token` and `user`. The `token` field is defined as non-nullable `string` type while the `user` field is defined as the non-nullable `User` type we created previously.

### Defining the `SignupUserInput` Type

We will define a new input object type that will be used to collect user's data anytime they want to register for a new account.

Add the following code below the `AuthPayLoad` type:

```
   const SignupUserInput = inputObjectType({
    name: "SignupUserInput",
    definition(t) {
      t.nonNull.string("email");
      t.nonNull.string("username");
      t.nonNull.string("password");
      t.nonNull.string("fullname");
    },
  });

```

We used Nexus `inputObjectType` function to create a new input object type called `SignupUserInput`. Then we defined the following fields inside the `SignupUserInput` type: `id`, `email`, `username`, `password` and `fullname`. Each field is defined as a non-nullable `string` type.

### Defining the `SigninUserInput` Type

Below the `SignupUserInput` type, let's create another input object type and name it `SigninUserInput` which will be used to collect login details from a user:

```
   const SignInUserInput = inputObjectType({
    name: "SignInUserInput",
    definition(t) {
      t.nonNull.string("usernameOrEmail");
      t.nonNull.string("password");
    },
  });

```

Within the SigninUserInput type, we defined two fields: `usernameOrEmail` and `password` and they are both defined as non-nullable `string` type.

### The Special Root Operation Types in GraphQL

In GraphQL, there are three special root operation types, they are: `query`, `mutation`, and `subscription`. These operation types represent the entry points for executing operations in the GraphQL API.

#### Query Type

The `Query` type is used for read-only operations, such as retrieving data from the server. You can define fields on the `Query` type to allow clients to request specific pieces of data from the server.

#### Mutation Type

The `Mutation` type is used for write operations, such as creating, updating, or deleting data. You can define fields on the `Mutation` type to allow clients to make changes to the data stored on the server.

#### Subscription Type

The `Subscription` type is used for long-lived connections that allow the server to push updates to the client in real-time. You can define fields on the `Subscription` type to allow clients to subscribe to specific types of updates.

### Signing Up A New User

To sign up a new user, we will create a new type called `SignupUser` that will be used to register them.

Let's add the following snippets below the `SignInUserInput` type:

```
  export const SignupUser = extendType({
    type: 'Mutation',
    definition(t) {
      t.nonNull.field('signupUser', {
        type: AuthPayLoad,
        args: {
          data: nonNull(SignupUserInput)
        },
        async resolve(_parent, args, { res }, _info) {
          const { data: { email, username, password, fullname } } = args;

          if (!validator.isEmail(email)) {
            throw new GraphQLError('Please enter a valid E-mail!', {
              extensions: {
                code: 'UNAUTHENTICATED',
                http: { status: 401 },
              }
            });
          }
          if (!validator.isLength(password, { min: 5 })) {

            throw new GraphQLError('Password must have atleast 5 characters', {
              extensions: {
                code: 'UNAUTHENTICATED',
                http: { status: 401 },
              }
            });
          }
          if (!validator.isLength(username, { min: 3 })) {
            throw new GraphQLError('username must have at least 3 characters', {
              extensions: {
                code: 'UNAUTHENTICATED',
                http: { status: 401 },
              }
            });
          }
          if (!validator.isLength(fullname, { min: 4 })) {
            throw new GraphQLError('fullname have atleast 4 characters', {
              extensions: {
                code: 'UNAUTHENTICATED',
                http: { status: 401 },
              }
            });
          }

          const encryptedPassword = bcrypt.hashSync(password, 10);
          const userData = {
            email: email.toLowerCase(),
            username: username.toLowerCase(),
            fullname,
            password: encryptedPassword,
          };
          try {
            const usernameExist = await userModel.findOne({ username: username.toLowerCase() });
            if (usernameExist) {
              throw new GraphQLError('Username is already in use!', {
                extensions: {
                  code: 'UNAUTHENTICATED',
                  http: { status: 401 },
                }
              });
            }
            const emailExist = await userModel.findOne({ email: email.toLowerCase() });
            if (emailExist) {
              throw new GraphQLError('Email is already in use!', {
                extensions: {
                  code: 'UNAUTHENTICATED',
                  http: { status: 401 },
                }
              });
            }
            const createdUser = await userModel.create(userData);
            console.log(createdUser)
            const token = jsonwebtoken.sign(
              {
                id: createdUser._id,
                username: createdUser.username,
                exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24,
              },
              process.env.JWT_SECRET!
            );
            res
               .cookie("access_token", token, {
                httpOnly: true,
                secure: process.env.SECURE_COOKIE === 'true'? true : false,
                maxAge: Math.floor(Date.now() / 1000) + (60 * 60 * 24),
                sameSite: 'none'
               })
            return {
              user: createdUser,
              token,
            };
          } catch (error) {
            throw error
          }
        },
      })
    }
  });

```

#### Demystifying the `SignupUser` code

In the following snippets:

```
 export const SignupUser = extendType({
    type: 'Mutation',
    definition(t) {
      t.nonNull.field('signupUser', {
        type: AuthPayLoad,
        args: {
          data: nonNull(SignupUserInput)
        },

```

- We defined a new field called `signupUser` that extends the GraphQL root `Mutation` root type.

- the return type for the `signupUser` mutation was defined as the `AuthPayLoad` type we created previously.

- then we defined the expected argument as `data` object whose value is expected to be a non-nullable `SignupUserInput` object type that will be passed to the `signupUser` field.

In the snippet below:

```
async resolve(_parent, args, { res }, _info) {
          const { data: { email, username, password, fullname } } = args;

          if (!validator.isEmail(email)) {
            throw new GraphQLError('Please enter a valid E-mail!', {
              extensions: {
                code: 'UNAUTHENTICATED',
                http: { status: 401 },
              }
            });
          }
          if (!validator.isLength(password, { min: 5 })) {

            throw new GraphQLError('Password must have atleast 5 characters', {
              extensions: {
                code: 'UNAUTHENTICATED',
                http: { status: 401 },
              }
            });
          }
          if (!validator.isLength(username, { min: 3 })) {
            throw new GraphQLError('username must have at least 3 characters', {
              extensions: {
                code: 'UNAUTHENTICATED',
                http: { status: 401 },
              }
            });
          }
          if (!validator.isLength(fullname, { min: 4 })) {
            throw new GraphQLError('fullname have atleast 4 characters', {
              extensions: {
                code: 'UNAUTHENTICATED',
                http: { status: 401 },
              }
            });
          }

```

- We created a resolver function called `resolve` for the `signupUser` field.

1. The `parent` is the return value of the resolver for this field's parent.

2. The `args` is the GraphQL arguments provided for this field.

3. The `context` is used for sharing data that the resolvers might need. We created this function earlier in the server file and then passed the express response object (`res`) to it. The `res` object will be made available to all the `resolver` functions through the `context` object.

You might have noticed the `{res}` instead of `context` in the `signupUser` resolver function arguments, that is because we unpacked the `context` parameter using [Object Destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#object_destructuring) method.

Instead of having the `context` in the parameters, and then later doing something like this:

```
const res = context.res

```

Or this:

```
const {res} = context

```

We just unpacked the `res` object directly in the arguments.

1. The `info` contains details on the present status of the operation.

The underscore (`_`) was used to prefix `_parent` and `_info` parameters to bypass the linter error because they are unused. We don't need the `parent` and `info` parameters now, removing them will cause an error because the four parameters are expected in that position, and not using them will result to the linter warning us about unused parameters. To fix all of the above problems, we prefixed the unused parameters with an underscore (`_`).

In thefollowing line:

```
const { data: { email, username, password, fullname } } = args;

```

we unpacked the `email`, `username`, `password`, `fullname` from the `data` object passed into the `args`. We could also remove the `args` object from the resolve's argument like we did with the context and do something like this:

```
async resolve(_parent, { data: { email, username, password, fullname } }, { res }, _info) {
...

```

But doing so would only make the resolver arguments longer.

In the following snippets:

```
          if (!validator.isEmail(email)) {
            throw new GraphQLError('Please enter a valid E-mail!', {
              extensions: {
                code: 'UNAUTHENTICATED',
                http: { status: 401 },
              }
            });
          }
          if (!validator.isLength(password, { min: 5 })) {

            throw new GraphQLError('Password must have atleast 5 characters', {
              extensions: {
                code: 'UNAUTHENTICATED',
                http: { status: 401 },
              }
            });
          }
          if (!validator.isLength(username, { min: 3 })) {
            throw new GraphQLError('username must have at least 3 characters', {
              extensions: {
                code: 'UNAUTHENTICATED',
                http: { status: 401 },
              }
            });
          }
          if (!validator.isLength(fullname, { min: 4 })) {
            throw new GraphQLError('fullname have atleast 4 characters', {
              extensions: {
                code: 'UNAUTHENTICATED',
                http: { status: 401 },
              }
            });
          }

```

We used the `validator` library to verify the `email`, `password`, `username` and the `fullname` details provided by the user, and throw an `UNAUTHENTICATED` error using the `GraphQLError` library whenever the validations fail.

In the following code:

```
const encryptedPassword = bcrypt.hashSync(password, 10);
          const userData = {
            email: email.toLowerCase(),
            username: username.toLowerCase(),
            fullname,
            password: encryptedPassword,
          };

```

We encrypted the user's password using the `bcrypt` library. Then creates a `userData` object that stores the `email`, `username`, `fullname` and the encrypted `password` data.

In the following snippets:

```
try {
            const usernameExist = await userModel.findOne({ username: username.toLowerCase() });
            if (usernameExist) {
              throw new GraphQLError('Username is already in use!', {
                extensions: {
                  code: 'UNAUTHENTICATED',
                  http: { status: 401 },
                }
              });
            }
            const emailExist = await userModel.findOne({ email: email.toLowerCase() });
            if (emailExist) {
              throw new GraphQLError('Email is already in use!', {
                extensions: {
                  code: 'UNAUTHENTICATED',
                  http: { status: 401 },
                }
              });
            }
            const createdUser = await userModel.create(userData);

            const token = jsonwebtoken.sign(
              {
                id: createdUser._id,
                username: createdUser.username,
                exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24,
              },
              process.env.JWT_SECRET!
            );
            res
               .cookie("access_token", token, {
                httpOnly: true,
                secure: process.env.SECURE_COOKIE === 'true'? true : false,
                maxAge: Math.floor(Date.now() / 1000) + (60 * 60 * 24),
                sameSite: 'none'
               })
            return {
              user: createdUser,
              token,
            };
          } catch (error) {
            throw error
          }
        },
      })
    }
  });

```

We checked if the username exist already in the database by executing the following query:

```
const usernameExist = await userModel.findOne({ username: username.toLowerCase() });

```

If the `usernameExist`, we'll throw the `Username is already in use` error message. We also verified if the user's email address exists in the database by running the following query:

```
const emailExist = await userModel.findOne({ email: email.toLowerCase() });

```

If `emailExist` value is truthy, we'll return `Email is already in use` error to the user.

Then we created the user using the data stored in the `userData`'s variable by running the following query:

```
const createdUser = await userModel.create(userData);

```

We used the [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken) library with a secret key (we'll create the key very soon) to generate a secure token from the created user data using the following snippet:

```
            const token = jsonwebtoken.sign(
              {
                id: createdUser._id,
                username: createdUser.username,
                exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24,
              },
              process.env.JWT_SECRET!
            );

```

By default, when we create a new document in MongoDB, mongoose automatically adds an `_id` field that acts as a primary key. That was how we came about `createdUser._id`. To create your `JWT` secret key, open up the `.env` file and add the following below the `DATABASE_URL` :

```
DATABASE_URL='Your-MongoDB-URL-String-goes-here'
JWT_SECRET = 'Your-JWT_SECRET-goes-here'

```

Note that your JWT secret key is private to you, it must be very difficult to guess and must be secured. Whenever the server receives a JWT from the user's computer, the `JWT_SECRET` will be used to decrypt the user's token. We'll discuss more about how the authentication work soon.

In the following snippets:

```
res
               .cookie("access_token", token, {
                httpOnly: true,
                secure: process.env.SECURE_COOKIE === 'true'? true : false,
                maxAge: Math.floor(Date.now() / 1000) + (60 * 60 * 24),
                sameSite: 'none'
               })
            return {
              user: createdUser,
              token,
            };

```

We used the `res` object in express to store the `tokens` on the user's web browser [using HTTP cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#third-party_cookies). In part three of this tutorial, we will configure the browser to send the cookies back to the server anytime the user is making an API request.

We added some attributes to the cookie so that the browser will know how to handle the cookies when it receives them. Below are what they are:

- `httpOnly` : Setting this attribute to `true` will prevent the cookie from being accessed by JavaScript. The aim of this is to prevent [XSS Attack](https://en.wikipedia.org/wiki/Cross-site_scripting) on our app.

- `secure`: Setting this attribute to `true` is used to indicate to the browser that the cookies should only be transmitted over a secure (encrypted) connection. This will keep our app secure and prevent the [Man in the middle (MITM) attack](https://www.imperva.com/learn/application-security/man-in-the-middle-attack-mitm/) on it. Add the following to your `.env` file:

- `maxAge`: The maximum lifetime of the cookie before it expires, I set it to 24 hours

- `sameSite`: This option will only allow the cookie to be sent to the same domain name. I set it to `none` because the front end and the back end of this app will be hosted on different domains.

## Authenticating the User

A user needs to be authenticated anytime they make an API request to our GraphQL server. If not, we will have to keep redirecting the user to the login page and have them enter their username and password every time they want to perform any operation like fetching all notes, modifying and deleting them to confirm their identity.

Nobody should have to go through that stress. We need a secure way to authenticate a user each time they make an API request to the server and this is why we are using a **token-based authentication system.**

### What is a Token-Based Authentication System?

Token-Based Authentication is a secure way of verifying a user's identity using encrypted security tokens. Once the token has been generated after the initial logins, the token will be shared between the user's computer and the server rather than having the user re-enter their credentials every time they need to make a request to the server.

### How does it work?

- The user logins or signup for a new account

- The server verifies the user's credentials

- If the credentials are confirmed, the server generates a secure token using [JSON Web Token](https://jwt.io/)(JWT)

- The tokens are then placed on the user's computer (We used a [Cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) with the [`HttpOnly`](https://owasp.org/www-community/HttpOnly#:~:text=What%20is%20HttpOnly%3F,if%20the%20browser%20supports%20it).) attribute for this app)

- The next time a user makes an API request to access secured data from the browser, the JWT tokens will be attached to the request.

- The server then verifies the user's token before allowing the user to perform any operations.

- If the tokens are confirmed, access will be granted to the user

### Why use `Cookies` with `HttpOnly` attribute to store JSON Web Tokens?

Using HTML Web Storage to save the user's token is considered a bad idea because it makes the website vulnerable to XSS attack. An attacker can have access to an authenticated user's token by injecting JavaScript code that will run on the user's website. When `Cookies` are used with the `HttpOnly` attribute, they will not be accessible with JavaScript thereby making them invulnerable to XSS attack.

However, they can still be susceptible to [Cross Site Request Forgery (CSRF) attack](https://owasp.org/www-community/attacks/csrf) if we did not restrict access to the cookies by setting the `HttpOnly`, `secure` and `SameSite` flag.

## Signing In the User

To sign in a user, let's create a `SigninUser` type. This type will be used to login a verified user.

Add the following code below the `SignupUser` object inside the `src/graphql/User.ts` file:

```
    export const SigninUser = extendType({
    type: 'Mutation',
    definition(t) {
      t.nonNull.field('signinUser', {
        type: AuthPayLoad,
        args: {
         data: nonNull(
          SignInUserInput
         ),
        },
        async resolve(_parent, args, { res }, _info) {
         try {
           const { data: { usernameOrEmail, password } } = args

           const userFound = await userModel.findOne({
             $or: [{ email: usernameOrEmail.toLowerCase() },
             { username: usernameOrEmail.toLowerCase() }],
           }).exec();
           if (!userFound) {
             throw new GraphQLError('User does not exist', {
              extensions: {
                code: 'UNAUTHENTICATED',
                http: { status: 401 },
              }
            });
           }
           if (bcrypt.compareSync(password, userFound.password)) {
             const username = userFound.username;
             const id = userFound._id;
             const token = jsonwebtoken.sign({
               id,
               username,
               exp: Math.floor(Date.now() / 1000) + (60 * 60 * 24),
             }, process.env.JWT_SECRET!);
             res
               .cookie("access_token", token, {
                 httpOnly: true,
                 secure: true,
                 maxAge: Math.floor(Date.now() / 1000) + (60 * 60 * 24),
                 sameSite: 'none'
               })
             return {
               user: userFound,
               token,
             };
           }
           throw new GraphQLError('Incorrect password', {
            extensions: {
              code: 'UNAUTHENTICATED',
              http: { status: 401 },
            }
          });
         }
         catch (error: any) {
           throw new GraphQLError(error, {
            extensions: {
              code: 'UNAUTHENTICATED',
              http: { status: 401 },
            }
          });
         }
       }
     })
    }
  });

```

### Demystifying the `SigninUser` code

We created a new field called `signinUser`. Just like the `signupUser` field, it extends the GraphQL `Mutation` root type and returns a non-nullable `AuthPayLoad` type. It also has a non-nullable argument of `SignInUserInput` type.

Then we created a resolver function called `resolve` for the `signinUser` mutation. Inside the `resolve` function, we unpacked the `usernameOrEmail` and `password` arguments using object destructuring, then we checked if the `usernameOrEmail` exists in the database by running the following query:

```
const userFound = await userModel.findOne({
             $or: [{ email: usernameOrEmail.toLowerCase() },
             { username: usernameOrEmail.toLowerCase() }],
           }).exec();

```

A user can use their username or email to log in. The query will select one document where either the `username` field value is equal to `usernameOrEmail` or `email` field value is equal to `usernameOrEmail`. The query result will be saved into the `userFound` variable. If the `userFound` value is [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy), we'll throw a new `GraphQLError` that says: 'User does not exist'.

If the `userFound` value is not [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy), we'll compare the password in the database with the user's entered password using the `bcrypt` library. Once the password is confirmed, a new token will be generated with the user's `id` and `username` . After that, the generated token will be sent to the client's computer via `cookies`.

But if the user's password in the database does not match the entered password, then a `GraphQLError` that says: 'Incorrect password' will be returned to the user.

## Logging Out The User

To log the user out is very easy, all we have to do is delete the user's token from the cookies. Without the token, a user will have to login again to access their data.

Let's write the following code below the `SigninUser` object in `src/graphql/User.ts`:

```
  export const LogoutUser = extendType({
    type: "Mutation",
    definition(t) {
      t.nonNull.field("signoutUser", {
        type: "Boolean",
        async resolve (_parent, _args, { res }, _info) {
         res.clearCookie("access_token")
          return true
        },
      });
    },
  });

```

We created a new field that extends the `Mutation` root type called `signoutUser`. The return type for this field is defined as `Boolean`. Then we created a new resolver function called `resolve`. What this function does is very simple. It clears the `access_token` cookies, then returns a `true` boolean value to the client.

# Generating the Schema definition language (SDL) from the `User` Code

In this part, we are going to use Nexus to generate the GraphQL schema from all the user's code we've been writing.

Create a new TypeScript file named `index.ts` in `scr/graphql/`, then add the following export to it:

```
export * from "./User";

```

update `src/schema.ts` file to use all the imports coming from `scr/graphql/index.ts`:

```
import { makeSchema } from 'nexus'
import { join } from 'path'
import * as types from "./graphql";
export const schema = makeSchema({
  types,
  outputs: {
    schema: join(process.cwd(), "schema.graphql"), 
    typegen: join(process.cwd(), "nexus-typegen.ts"), 
  },
})

```

First, we imported everything in `scr/graphql/index.ts` file as `types` using the following snippets:

```
import * as types from "./graphql";

```

Then we update the `makeSchema` method to use the imported `types` instead of the empty array (`[]`) that was there before with the following snippets:

```
export const schema = makeSchema({
  types, // same as: types:types
...

```

Open up the `schema.graphql` file at the root directory of your project, If your terminal is still running on the dev server you should see that the file has been automatically updated:

```
### This file was generated by Nexus Schema
### Do not make changes to this file directly

type AuthPayLoad {
  token: String!
  user: User!
}

type Mutation {
  signinUser(data: SignInUserInput!): AuthPayLoad!
  signupUser(data: SignupUserInput!): AuthPayLoad!
}

type Query {
  ok: Boolean!
}

input SignInUserInput {
  password: String!
  usernameOrEmail: String!
}

input SignupUserInput {
  email: String!
  fullname: String!
  password: String!
  username: String!
}

type User {
  email: String!
  fullname: String!
  id: String!
  password: String!
  username: String!
}

```

If you open the `nexus-typegen.ts` file, you should see something like this too:

```
/**
 * This file was generated by Nexus Schema
 * Do not make changes to this file directly
 */

declare global {
  interface NexusGen extends NexusGenTypes {}
}

export interface NexusGenInputs {
  SignInUserInput: { // input type
    password: string; // String!
    usernameOrEmail: string; // String!
  }
  SignupUserInput: { // input type
    email: string; // String!
    fullname: string; // String!
    password: string; // String!
    username: string; // String!
  }
}

export interface NexusGenEnums {
}

export interface NexusGenScalars {
  String: string
  Int: number
  Float: number
  Boolean: boolean
  ID: string
}

export interface NexusGenObjects {
  AuthPayLoad: { // root type
    token: string; // String!
    user: NexusGenRootTypes['User']; // User!
  }
  Mutation: {};
  Query: {};
  User: { // root type
    email: string; // String!
    fullname: string; // String!
    id: string; // String!
    password: string; // String!
    username: string; // String!
  }
}

export interface NexusGenInterfaces {
}

export interface NexusGenUnions {
}

export type NexusGenRootTypes = NexusGenObjects

export type NexusGenAllTypes = NexusGenRootTypes & NexusGenScalars

export interface NexusGenFieldTypes {
  AuthPayLoad: { // field return type
    token: string; // String!
    user: NexusGenRootTypes['User']; // User!
  }
  Mutation: { // field return type
    signinUser: NexusGenRootTypes['AuthPayLoad']; // AuthPayLoad!
    signupUser: NexusGenRootTypes['AuthPayLoad']; // AuthPayLoad!
  }
  Query: { // field return type
    ok: boolean; // Boolean!
  }
  User: { // field return type
    email: string; // String!
    fullname: string; // String!
    id: string; // String!
    password: string; // String!
    username: string; // String!
  }
}

export interface NexusGenFieldTypeNames {
  AuthPayLoad: { // field return type name
    token: 'String'
    user: 'User'
  }
  Mutation: { // field return type name
    signinUser: 'AuthPayLoad'
    signupUser: 'AuthPayLoad'
  }
  Query: { // field return type name
    ok: 'Boolean'
  }
  User: { // field return type name
    email: 'String'
    fullname: 'String'
    id: 'String'
    password: 'String'
    username: 'String'
  }
}

export interface NexusGenArgTypes {
  Mutation: {
    signinUser: { // args
      data: NexusGenInputs['SignInUserInput']; // SignInUserInput!
    }
    signupUser: { // args
      data: NexusGenInputs['SignupUserInput']; // SignupUserInput!
    }
  }
}

export interface NexusGenAbstractTypeMembers {
}

export interface NexusGenTypeInterfaces {
}

export type NexusGenObjectNames = keyof NexusGenObjects;

export type NexusGenInputNames = keyof NexusGenInputs;

export type NexusGenEnumNames = never;

export type NexusGenInterfaceNames = never;

export type NexusGenScalarNames = keyof NexusGenScalars;

export type NexusGenUnionNames = never;

export type NexusGenObjectsUsingAbstractStrategyIsTypeOf = never;

export type NexusGenAbstractsUsingStrategyResolveType = never;

export type NexusGenFeaturesConfig = {
  abstractTypeStrategies: {
    isTypeOf: false
    resolveType: true
    __typename: false
  }
}

export interface NexusGenTypes {
  context: any;
  inputTypes: NexusGenInputs;
  rootTypes: NexusGenRootTypes;
  inputTypeShapes: NexusGenInputs & NexusGenEnums & NexusGenScalars;
  argTypes: NexusGenArgTypes;
  fieldTypes: NexusGenFieldTypes;
  fieldTypeNames: NexusGenFieldTypeNames;
  allTypes: NexusGenAllTypes;
  typeInterfaces: NexusGenTypeInterfaces;
  objectNames: NexusGenObjectNames;
  inputNames: NexusGenInputNames;
  enumNames: NexusGenEnumNames;
  interfaceNames: NexusGenInterfaceNames;
  scalarNames: NexusGenScalarNames;
  unionNames: NexusGenUnionNames;
  allInputTypes: NexusGenTypes['inputNames'] | NexusGenTypes['enumNames'] | NexusGenTypes['scalarNames'];
  allOutputTypes: NexusGenTypes['objectNames'] | NexusGenTypes['enumNames'] | NexusGenTypes['unionNames'] | NexusGenTypes['interfaceNames'] | NexusGenTypes['scalarNames'];
  allNamedTypes: NexusGenTypes['allInputTypes'] | NexusGenTypes['allOutputTypes']
  abstractTypes: NexusGenTypes['interfaceNames'] | NexusGenTypes['unionNames'];
  abstractTypeMembers: NexusGenAbstractTypeMembers;
  objectsUsingAbstractStrategyIsTypeOf: NexusGenObjectsUsingAbstractStrategyIsTypeOf;
  abstractsUsingStrategyResolveType: NexusGenAbstractsUsingStrategyResolveType;
  features: NexusGenFeaturesConfig;
}

declare global {
  interface NexusGenPluginTypeConfig<TypeName extends string> {
  }
  interface NexusGenPluginInputTypeConfig<TypeName extends string> {
  }
  interface NexusGenPluginFieldConfig<TypeName extends string, FieldName extends string> {
  }
  interface NexusGenPluginInputFieldConfig<TypeName extends string, FieldName extends string> {
  }
  interface NexusGenPluginSchemaConfig {
  }
  interface NexusGenPluginArgConfig {
  }
}

```

If the file has not been updated, you might need to restart your server by running the following command:

```
npm run dev

```

Or you can run the `generate` script if you don't feel like starting the server to update the code:

```
npm run generate

```

# Verifying a User's JSON Web Tokens

Before we can create a new note or make any modifications to it, we need to verify the owner of the note first. We will be verifying the owner by decrypting the owner's JSON Web Tokens. The decrypted JWT contains the user's `id` and `username`.

The `id` will be used to uniquely identify each user and their sticky notes. The user will be able to create, fetch, edit, and delete any notes that belong to them using the `id`.

Open up the `src/index.ts` file and import the `jsonwebtoken` library:

```
import jsonwebtoken from 'jsonwebtoken'

```

After the import, we'll create a new function that verifies the user's token right above the `startApolloServer()` function:

```
const getUser = (token: string, res: Response) => {
  try {
    if (token) {
      return jsonwebtoken.verify(token, process.env.JWT_SECRET!)
    }
    res.clearCookie("access_token");
    return null
  } catch (error) {
    return null
  }
}

```

The `getUser` function takes a user's token and the Express response object as parameters, then verifies and returns the user's token.

`Null` will be returned if either no token was supplied or if an error occurred while verifying the user's token.

Now, let's update the `context` object to return the decoded JWT so that it can be used in the resolver function:

```
      context: async ({ req, res }:{req:Request, res:Response})=> {
        if (req) {
          const token = req.cookies.access_token || ""
          const user = getUser(token, res);
          return {
            user,
            res
          };
        }
        return {
          res
        }
      }

```

If a request is sent to our GraphQL server, we will get the tokens from the request cookies using `req.cookies.access_token` or return an empty string(`""`) if non was found using:

```
req.cookies.access_token || ""

```

We stored the decoded tokens into the `user` variable. Both the `user` and `res` object will be sent to the `context` object whenever a user makes a `request` to the server; otherwise, we'll simply return only the `res` object.

Let's update the express import to include the `Request` object:

```
import express, { Response, Request } from "express";

```

## Defining the Types for the Sticky Note

Let's create a new file in `src/graphql` called `Note.ts` and import the following objects into it:

```
import { extendType, objectType, inputObjectType, nonNull } from "nexus";
import noteModel from './../models/note';
import {GraphQLError} from "graphql"

```

We've already discussed what `extendType`, `objectType`, `inputObjectType`, `nonNull` and `GraphQLError` are used for when we are creating the User types. In the second line, we imported the `noteModel` we created previously.

Below the imports, let's create a new `objectType` called `Note` and a new `inputObjectType` called `NoteData`.

```
const Note = objectType({
    name: "Note",
    definition(t) {
        t.string("id");
        t.nonNull.string("content");
        t.nonNull.string("color");
        t.nonNull.string("userId");
    },
});
const NoteData = inputObjectType({
    name: "NoteData",
    definition(t) {
        t.nonNull.string("content");
        t.nonNull.string("color");
    },
});

```

The `Note` type has the following fields: `id`, `content`, `color`, and `userId` that can be used to query the notes.

Also, we defined the following fields: `content` and `color` in the `NoteData` type. Both fields have non-nullable string type.

## Fetching All Notes Belonging to a User

In this part, we will create our first GraphQL `query` that will be used to fetch every note that belongs to a user.

Type the following code below the `NoteData` object.

```
export const AllNotes = extendType({
    type: 'Query',
    definition(t) {
        t.nonNull.list.field('allNotes', {
            type: Note,
            async resolve(_parent, _args, context, _info) {
                try {
                    const { user } = context;
                    if (!user) {
                        throw new GraphQLError('You are not authenticated', {
                            extensions: {
                              code: 'UNAUTHENTICATED',
                              http: { status: 401 },
                            }
                          });
                    }
                    const notes = await noteModel.find(
                        {
                            userId: user.id,
                        }
                    );
                    console.log(notes)
                    return notes;
                }
                catch (error) {
                    throw error;
                }
            }
        })
    }
});

```

Using the Nexus `extendType` function, we extended the GraphQL `query` root type and added a new root field to it called `allNotes`. This time, we extended the GraphQL `query` root type instead of the `mutation` type because we are only fetching from the database and no modifications will be made to the note data.

We defined the return type of the query as an array of the `Note` type we created previously. Then we created a resolver function for the `allNotes` query named `resolve`. We performed all the operations that fetch the user's note inside the resolver function.

The `context` is the only argument we're using on this field's resolver function, so we prefixed `_parent`, `_args`, and `_info` with an underscore(`_`). Using the object destructuring method, we unpacked the `user` object that was passed down to the `resolver` via the `context` object.

In the following snippets:

```
if (!user) {
    throw new GraphQLError('You are not authenticated', {
       extensions: {
          code: 'UNAUTHENTICATED',
             http: { status: 401 },
                }
             });
          }

```

We checked if the `user` object passed down from `context` is valid. Remember, we returned a [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) value if the token is empty or not successfully decoded, and we returned the `user` if the token was successful in the `getUser` function.

If the `user` value is [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy), we will throw an `UNAUTHENTICATED` error, but if the user is valid, then we will fetch all the notes that belong to the user by executing the following Mongoose query:

```
 const notes = await noteModel.find(
                     {
                            userId: user.id,
                        }
                    );

```

The query above will search for all the notes whose `userId` matches the id of the user that is currently logged in, then stores the results into the `notes` variable.

## Fetching a Single Note

Let's create another query that will be used to fetch a single note for an authenticated user. The user will have to provide the `id` of the note to be fetched. Add the following code below the `AllNotes` type:

```
export const SingleNote = extendType({
    type: 'Query',
    definition(t) {
        t.nonNull.field('singleNote', {
            type: Note,
            args: {
                noteId: 'String',
            },
            async resolve(_parent, args, context, _info) {
                const { noteId } = args;
                const { user } = context;
                const userId = user.id
                if (!user) {
                    throw new GraphQLError('You are not authenticated', {
                        extensions: {
                          code: 'UNAUTHENTICATED',
                          http: { status: 401 },
                        }
                      });
                }
                try {
                    const noteDetails = await noteModel.findOne({ _id: noteId, userId });
                    if(!noteDetails){
                        throw new Error("Note not found");
                    }
                    return noteDetails;
                } catch (error) {
                    throw error;
                }
            },
        })
    }
});

```

We extended the `Query` root type again and added a new root field to it called `singleNote`. After that, we defined the return type of the `singleNote` query as a non-nullable `Note` `objectType`. Also, we defined the expected argument for the `singleNote` query (`noteId`) as a `string`. Then we created a resolver function named `resolve` that query the database for the note.

We used the following query:

```
const noteDetails = await noteModel.findOne({ _id: noteId, userId });

```

to fetch the first note that matches the `id` and the `userId` provided then stores the query result into `noteDetails` variable. We used the following snippets to throw a not found error if the note was not found:

```
if(!noteDetails){
                        throw new Error("Note not found");
                    }

```

## Creating A New Note

Now that we are done implementing the `singleNote` query, let's create our first `mutation` for the sticky note by adding a new type that will be used to create a new note.

Type the following code below the `SingleNote` type:

```
export const NewNote = extendType({
    type: 'Mutation',
    definition(t) {
        t.nonNull.field('newNote', {
            type: Note,
            args: {
                data: nonNull(NoteData)
            },
            async resolve(_parent, args, context, _info) {
                const { data } = args;
                const { user } = context;
                try {
                    if (!user) {
                        throw new GraphQLError('You are not authenticated', {
                            extensions: {
                              code: 'UNAUTHENTICATED',
                              http: { status: 401 },
                            }
                          });
                    }
                    const createdNoted = await noteModel.create({
                        content: data.content, color: data.color || 'yellow', userId: user.id
                    });
                    return createdNoted;
                } catch (error) {
                    throw error;
                }
            }
        })
    }
});

```

We extended the `Mutation` root type and added a new root field to it called `newNote`. Then we defined the return type of the `newNote` mutation as non-nullable `Note` type. Also, we defined the expected argument as a non-nullable `noteData` type. After that, we created a resolver function that creates the user's note if the user is authenticated.

We used the following queries:

```
const createdNoted = await noteModel.create({
                        content: data.content, color: data.color || 'yellow', userId: user.id
                    });

```

to create a new note document with the provided `content`, `color` and `userId` by using the `create()` function in Mongoose.

The `data.color || 'yellow'` means yellow will be used as the default `color` if the `color` args wasn't provided.

## Modifying an existing Note

In this part, we are going to create a new type that edits an existing note belonging to a user. To know the specific note that the user wants to modify, the note `id` must be provided as an argument.

Let's add the following code below the `NewNote` type.

```
export const ModifyNote = extendType({
    type: 'Mutation',
    definition(t) {
        t.nonNull.field('modifyNote', {
            type: Note,
            args: {
                noteId: nonNull('String'),
                data: nonNull(NoteData)
            },
            async resolve(_parent, args, context, _info) {
                const { noteId, data: { content, color } } = args;
                const { user } = context;
                if (!user) {
                    throw new GraphQLError('You are not authenticated', {
                        extensions: {
                          code: 'UNAUTHENTICATED',
                          http: { status: 401 },
                        }
                      });
                }
                try {
                    const modifiedNote = await noteModel.findOneAndUpdate(
                        {
                            _id: noteId, userId: user.id,
                        },
                        { content, color },
                        {
                            runValidators: true,
                            new: true,
                        }
                    );
                    if (!modifiedNote) {
                        throw new Error("Unable to update note");
                    }
                    console.log(modifiedNote)
                    return modifiedNote;
                } catch (error) {
                    throw error;
                }
            }
        })
    }
});

```

Here's the summary of what we did in the code above:

- We created a new field called `modifyNote` that extends the GraphQL `Mutation` root type.

- We defined `Note` type as the return type for the field

- we defined `noteId` and `data` as the expected argument for the `modifyNote` mutation. The `noteId` will be used used to search for the specific note that a user wants to modify. The note `data` will be used to make changes to the note when found.

- The `noteId` argument is a non-nullable `String` while the type for the `data` argument is non-nullable `NoteData` type.

- We created a resolver function named `resolve`.

- The resolver function checks if a user is authenticated, then modifies the user's note if the user is confirmed.

- The following query:

```
 const modifiedNote = await noteModel.findOneAndUpdate(
                        {
                            _id: noteId, userId: user.id,
                        },
                        { content, color },
                        {
                            runValidators: true,
                            new: true,
                        }
                    );

```

- uses the `findOneAndUpdate` function in Mongoose to search for the first note that matches the `noteId` and `userId` provided in the arguments.

- by default, Mongoose does not run validation on updates. We need to set the `runValidators` option to `true` every time we call the `findOneAndUpdate` or any `update` method to run the update validators on the query.

- also by default, `findOneAndUpdate` does not return the updated document, it returns the document as it was before the update was applied. Setting the `new` option to `true` will return the updated document instead of the old document.

- we saved the result of the query to `modifiedNote` variable.

- Then return the `modifiedNote` if the update was successful or throw the `Unable to update note` error if it wasn't.

## Deleting a Note

Let's perform the final `mutation` on this app by creating a new type that deletes a user's note. Only notes that the user has added can be deleted. To delete a note, the `noteId` must be provided.

Type the following code below the `ModifyNote` type:

```
export const DeleteNote = extendType({
    type: 'Mutation',
    definition(t) {
        t.nonNull.field('deleteNote', {
            type: Note,
            args: {
                noteId: nonNull('String')
            },
            async resolve(_parent, args, context, _info) {
                const { noteId } = args;
                const { user } = context;
                if (!user) {
                    throw new GraphQLError('You are not authenticated', {
                        extensions: {
                          code: 'UNAUTHENTICATED',
                          http: { status: 401 },
                        }
                      });
                }
                try {
                    const deletedNote = await noteModel.findOneAndDelete({
                        _id: noteId,
                        userId: user.id,
                    });
                    if (!deletedNote) {
                        throw new Error(" Note not found");
                    }
                    return deletedNote;
                } catch (error) {
                    throw error;
                }
            },
        })
    }
});

```

- We extended the `Mutation` root type and added a new root field to it called `deleteNote`

- The return type for the `deleteNote` field is defined as non-nullable `Note` type.

- We also defined the `noteId` as the expected argument for the `deleteNote` mutation

- the `noteId` has a non-nullable `String` type

- We created a resolver function named `resolve`.

- The resolver function deletes the note after verifying the user

- the following query:

```
const deletedNote = await noteModel.findOneAndDelete({
                        _id: noteId,
                        userId: user.id,
                    });

```

searches for the first note that matches the `noteId` and the `userId,` deletes it, and then stores the deleted note into the `deletedNote` variable.

- We return an error if the delete wasn't successful

- or return the deleted note if the note was successfully removed from the database.

## Generating the Note Schemas with Nexus

Let's connect all our exported note types to the `makeSchema` function. Open the `src/graphql/index.ts` file and add the following code below the user export that was there before:

```
export * from "./Note";

```

So everything looks like this:

```
export * from "./User"; 
export * from "./Note";

```

If we check the `schema.graphql` file now, we will notice that all the note types we created earlier have been converted to GraphQL schemas by Nexus:

```
### This file was generated by Nexus Schema
### Do not make changes to this file directly

type AuthPayLoad {
  token: String!
  user: User!
}

type Mutation {
  deleteNote(noteId: String!): Note!
  modifyNote(data: NoteData!, noteId: String!): Note!
  newNote(data: NoteData!): Note!
  signinUser(data: SignInUserInput!): AuthPayLoad!
  signupUser(data: SignupUserInput!): AuthPayLoad!
}

type Note {
  color: String!
  content: String!
  id: String
  userId: String!
}

input NoteData {
  color: String!
  content: String!
}

type Query {
  allNotes: [Note]!
  singleNote(noteId: String): Note!
}

input SignInUserInput {
  password: String!
  usernameOrEmail: String!
}

input SignupUserInput {
  email: String!
  fullname: String!
  password: String!
  username: String!
}

type User {
  email: String!
  fullname: String!
  id: String!
  password: String!
  username: String!
}

```

These are the GraphQL Schema Definition Language (SDL) codes that were generated by Nexus from the `User` and `Note` type codes that we wrote previously.

The `AuthPayLoad` type represents the payload that is returned when a user successfully signs in or signs up. It has two fields: `token` (which is a string representing an authentication token) and `user` (which is an object of type `User`).

The `Note` type represents a note object. It has four fields: `id`, `content`, `color`, and `userId`.

The `NoteData` input type represents the data that is passed as an argument to the `newNote` and `modifyNote` mutations. It has two fields: `content` and `color`.

The `SignInUserInput` and `SignupUserInput` input types represent the data that is passed as an argument to the `signinUser` and `signupUser` mutations. They have different fields, depending on the information that is needed to sign in or sign up.

The `User` type represents a user object. It has five fields: `id`, `email`, `fullname`, `password`, and `username`.

The `Mutation` type represents the root type for GraphQL mutations in this API. It has five fields: `deleteNote`, `modifyNote`, `newNote`, `signinUser`, and `signupUser` each of which corresponds to a different mutation that can be performed.

The `Query` type represents the root type for GraphQL queries in this API. It has two fields: `allNotes` and `singleNote`.

# Testing the GraphQL APIs with Postman

One of the easiest and fastest ways to test our GraphQL API endpoints locally is through [Postman](https://www.postman.com/). Postman is a powerful software for testing API requests, inspecting their responses, and easily debugging them. Click [here](https://www.postman.com/downloads/) to download and install it if you don't have it already.

## Changing the `Secure` Cookie Attribute to `false` for testing purposes only

We need to modify a few settings specifically for these tests in order to test our APIs.

Open the `.env` file and change the `SECURE_COOKIE` from `true` to `false`. Without changing it, Postman will not send the cookies along with the request any time an authenticated user tries to access a secure endpoint (or maybe Postman sent it but the server rejected).

Changing the setting like that will make our website vulnerable, but we are only doing this for testing our API in development mode only. During production, the `secure` attribute must be set back to `true` because we set the `sameSite` attribute to `none`. Doing this is one of many ways to prevent [Cross-site request forgery](https://www.imperva.com/learn/application-security/csrf-cross-site-request-forgery/) on the website.

# Testing the `signupUser` Mutation

Launch your Postman and make sure your development server is running already at `http://localhost:4000/graphql`. If it isn't, run the following command to start it:

```
npm run dev

```

Create a new collection by clicking on the `Collections` tab, then click on the plus icon. You will be prompted to give your collection a name, let's name it `Sticky Notes` for now. You can rename it later if you prefer. Right-click on the created `Sticky Notes` collection or click on the three dots option button on it, then select `Add request` from the drop-down options. Name the request `SignupUser`. After that, change the request method from `GET` to `POST`. The `Post` request method is the method we are going to use throughout our test.

Enter `http://localhost:4000/graphql` in the URL text box. Below the URL textbox, select `Body` from the options tab, then select the `GraphQL` radio button.

After that, you will be presented with two text-area boxes named: `QUERY` and `GRAPHQL VARIABLES`.

Type the following queries into the `QUERY` box:

```
mutation SignupUser($data: SignupUserInput!) {
  signupUser(data: $data) {
    token
    user {
      email
      fullname
      id
      password
      username
    }
  }
}

```

And type the following into `GRAPHQL VARIABLES`

```
{
  "data": {
    "email": "janedoe@email.com",
    "fullname": "Jane Doe",
    "password": "invisible",
    "username": "janedoe"
  }
}

```

Then click on the **Send** button

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1670983010718/Zq6T6rQhf.png)

After a successful registration, click on the `Cookies` below the **Send** button, you will see the `access_token` cookies sent from our GraphQL server. This token will be automatically attached to every request from now until we signout or maybe when it expires.

# Testing the `signinUser` Mutation

To test the `signinUser` mutation, we'll add another request to the **Sticky Notes** collection and name it `SigninUser`. Then change the request method from `GET` to `POST`, and enter `http://localhost:4000/graphql` in the URL textbox. Select `Body` in the options tab below the URL textbox, then check the `GraphQL` radio button.

Type the following queries into the `QUERY` box:

```
mutation SignupUser($data: SignInUserInput!) {
  signinUser(data: $data) {
    token
    user {
      email
      fullname
      id
      password
      username
    }
  }
}

```

And type the following into the `GRAPHQL VARIABLES`:

```
{
  "data": {
    "password": "invisible",
    "usernameOrEmail": "janedoe"
  }
}

```

Then click on the **Send** button:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1671878914092/ed75701c-8567-448e-989d-1171b52474ce.png)

# Testing the `newNote` Mutation

To test the `NewNote` mutation, we'll go through the same process we've been following since, we'll add another request to the **Sticky Notes** collection and name it `NewNote`. Then change the request method from `GET` to `POST`, and enter `http://localhost:4000/graphql` in the URL textbox. Select `Body` in the options tab below the URL textbox, then check the `GraphQL` radio button.

Type the following queries into the `QUERY` box:

```
mutation NewNote($data: NoteData!) {
  newNote(data: $data) {
    color
    content
    id
    userId
  }
}

```

GRAPHQL VARIABLES:

```
{
  "data": {
    "content": "sample",
    "color": "yellow"
  }
}

```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1671879006099/32dc0906-d78d-4317-b6b1-b56f03dc580b.png)

# Testing the `modifyNote` Mutation

Add another request to the **Sticky Notes** collection and name it `ModifyNote` . Then change the request method from `GET` to `POST`, and enter `http://localhost:4000/graphql` in the URL textbox. Select `Body` in the options tab below the URL textbox, then check the `GraphQL` radio button.

Type the following queries into the `QUERY` box:

```
mutation ModifyNote($noteId: String!, $data: NoteData!) {
  modifyNote(noteId: $noteId, data: $data) {
    color
    content
    id
    userId
  }
}

```

**GRAPHQL VARIABLES:**

```

  { "noteId": "639a7cf1946da6008c0aba01",
  "data": {
    "content": "Note sample modified",
    "color": "green"
  }
}

```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1671879064982/48c35e88-8a75-4db4-963a-f7070ea9a14a.png)

# Testing the `allNotes` Query

To fetch all the sticky notes belonging to a user, add another request to the **Sticky Notes** collection and name it `AllNotes`. Then change the request method from `GET` to `POST`, and enter `http://localhost:4000/graphql` in the URL textbox. Select `Body` in the options tab below the URL textbox, then check the `GraphQL` radio button.

Type the following queries into the `QUERY` box:

```
query AllNotes {
  allNotes {
    id
    content
    color
    userId
  }
}

```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1671879427729/afc23383-55f8-4386-9e72-6558bfc1ffb8.png)

# Testing the `deleteNote` Mutation

To delete a note, add another request to the **Sticky Notes** collection and name it `DeleteNote`. Then change the request method from `GET` to `POST`, and enter `http://localhost:4000/graphql` in the URL textbox. Select `Body` in the options tab below the URL textbox, then check the `GraphQL` radio button.

Type the following queries into the `QUERY` box:

```
mutation Mutation($noteId: String!) {
  deleteNote(noteId: $noteId) {
    color
    content
    userId
    id
  }
}

```

GRAPHQL VARIABLES:

```
{
  "noteId": "639a7cf1946da6008c0aba01"
}

```

# Testing the `signoutUser` Mutation

To test the `signoutUser` Mutation, add another request to the **Sticky Notes** collection and name it `DeleteNote`. Then change the request method from `GET` to `POST`, and enter `http://localhost:4000/graphql` in the URL textbox. Select `Body` in the options tab below the URL textbox, then check the `GraphQL` radio button.

Type the following queries into the `QUERY` box:

```
mutation signoutUser {
  signoutUser
}

```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1671881153780/5181329e-81ab-467f-b0d6-ba158368378c.png)

When you click on "Cookies" below the send button after signing out the user, you will see that the access token cookies have been deleted:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1671936681340/5076a3f2-09db-458a-8cd2-9468978479a9.png)

# Summary and Next Steps

In this section, we built the GraphQL server for the Sticky Note app using Apollo Server 4, Express.js, and MongoDB, following the code-first approach. We then defined various types and fields that can be used to query and modify the API. Finally, we tested the GraphQL APIs using Postman.

In the next section, we will make the frontend aspect built in the previous part communicate with the GraphQL APIs built in this section.