[Outdated] How I build a full-fledged app with Next.js and MongoDB Part 1: User authentication
Published: 2019-08-09 14:38:02

## This does not reflect the recent rewrite of `nextjs-mongodb-app`. Check out the [latest version](https://dev.to/hoangvvo/how-i-build-a-full-fledged-app-with-next-js-and-mongodb-part-1-user-authentication-using-passport-js-4b16).

**Update**: I have replaced argon2 with bcryptjs for compatibility.

For the last month, I have been struggling with implementing an authentication system for my next project. It is part of an organization that I found to tackle problems in the community with code.

The project is an **online** game that promotes good behaviors, so it needs to have a way for users to login.

I am using [Next.js](https://nextjs.org/ "Next.JS"), a React framework, and [MongoDB](https://www.mongodb.com "MongoDB") as my database.

I used to have an Express backend as a Next.js custom server, where I (lazily) use PassportJS to handle authentication. I realized that I did not much control of my APIs doing so. Therefore, I decided to go for my authentication system. 

A possible counter-argument is that I should not reinvent the wheel. Still, I wanted exploration and challenges, so I took the path.

Most of the solutions I found online are either relied on 3rd party services (Google, Facebook, Identity as a Service) or half-baked without much thought into the real-life application. Thus, I decide to make my own.

Below are the Github repository and a demo for this project to follow along.

[Github repo](https://github.com/hoangvvo/nextjs-mongodb-app "nextjs mongodb app")

[Demo](https://nextjs-mongodb-app.hoangvvo.now.sh/) (Fixed)

## About `nextjs-mongodb-app` project

> nextjs-mongodb-app is a full-fledged app built with Next.JS and MongoDB. Most tutorials on the Internet are either half-baked or not production-ready. This project aims to fix that.
> 
> This project goes even further and attempts to integrate top features as seen in real-life apps, making it a full-fledged app.

For more information, visit the [Github repo](https://github.com/hoangvvo/nextjs-mongodb-app "nextjs mongodb app").

## Getting started

Typical tutorials will tell you do to `npm install next` or whatsoever, but I trust you to be an experienced developer already.

I will start by getting right into scaffolding my application.

### Environmental variables

This project retrieves the variable from `process.env`. You will need to implement your strategy. Some of the possible solutions are:

- https://github.com/zeit/next.js/tree/canary/examples/with-dotenv
- Setting the environment variables in your service provider (such as now.sh env, or heroku config var)

Required environmental variables in this project includes:

- process.env.**MONGODB_URI**

### Request library

This project will need to have a request library to make requests to API. Feel free to pick your own. Here are some suggestions:

- [axios](https://www.npmjs.com/package/axios "Axios") (my choice)
- [isomorphic-unfetch](https://www.npmjs.com/package/isomorphic-unfetch "isomorphic unfetch")
- [request](https://www.npmjs.com/package/request "request")

### Validation library

I'm using [validator](https://www.npmjs.com/package/validator "Validator") for validation, but feel free to use your library or write your check.

### Password hashing library

Password must be hashed. Period. There are different libraries out there:

- [bcrypt](https://www.npmjs.com/package/bcrypt "bcrypt") (most common)
- scrypt
- [argon2](https://www.npmjs.com/package/argon2 "argon2") (my choice)

And, no MD5, SHA1, or SHA256, please!

### Mongoose... I mean MongoDB

*Mongoose* is not *MongoDB*. It is a whole different avenue. I see a lot of tutorials use Mongoose and MongoDB interchangeably, and that is so wrong.

*Mongoose* is an *Object data modeling* library. This ensures persistence and integrity of data by defining schemas in your database, which, in my opinion, destroys the purpose of __NoSQL__. 

*NoSQL* is designed to drop validation and modeling (which is known as [ACID](https://en.wikipedia.org/wiki/ACID "ACID")). It means any type of data is accepted and future modification of the structure is flexible. On the other hand, *Mongoose* will require you to have a schema for everything. If I define to my `Age` field to be `Number`, trying to save a `String` to the field will generate an Error.

Be aware that using Mongoose has a [significant decrease in performance](https://medium.com/@bugwheels94/performance-difference-in-mongoose-vs-mongodb-60be831c69ad "Mongoose vs MongoDB performance") because data must be validated in every READ and WRITE (which justify dropping `ACID`).

In case you are going to use *Mongoose*, here is the schema I have for `User`:

```javascript
const UserSchema = new mongoose.Schema({
  email: {
    type: String,
    trim: true,
    minlength: 1,
    unique: true,
    index: true,
  },
  emailVerified: {
    type: Boolean,
  },
  password: {
    type: String,
    minlength: 6,
  },
  name: {
    type: String,
  },
 });
 
 export default mongoose.models.User || mongoose.model('User', UserSchema);
```

## Building the full-fledged authentication.

### Response schema

> measure twice, cut once

I just cannot stress that enough. I would like to see a persistency in my APIs' responses. Thus, I come up with this schema - a standard response for every APIs:

```json
{
  "status": "ok / error",
  "message": "a user-readable message",
  "data": "<payload object>"
}
```

Feel free to have your own. For some inspirations, see this [Stackoverflow discussion](https://stackoverflow.com/questions/12806386/standard-json-api-response-format "Standard JSON API response format?").

If I do not have this planned out, I may run into several problems. Imagine one of my endpoints responses with:

```json
{
  "success": true,
  "msg": "successful stuff",
}
```

...while another one responses with:

```json
{
  "error": false,
  "text": "ok",  
}
```

I will have to write two different checks and tries to display the messages using two different field:

```javascript
if (response.success === true || response.error === false) {
  alert(response.msg || response.text);
}
```

It will get bad quick especially when the endpoints grow in number and get undocumented.

### Middlewares

You may be familiar with the term *middleware* if you have an `ExpressJS` background.

The way to use Middlewares in Next.js is to take the original `handler` (API Route function of `(req, res)`) an as argument and return a new `handler` with additional functionality.

#### Database middleware

We will need to have a middleware that handles database connection since we do not want to call out the database in every route.

Creating `middlewares/withDatabase.js`:

```javascript
import { MongoClient } from 'mongodb';

const client = new MongoClient(process.env.MONGODB_URI, { useNewUrlParser: true });

const withDatabase = handler => (req, res) => {
  if (!client.isConnected()) {
    return client.connect().then(() => {
      req.db = client.db('nextjsmongodbapp');
      return handler(req, res);
    });
  }
  req.db = client.db('nextjsmongodbapp');
  return handler(req, res);
};
export default withDatabase;
```

What happens is that I first only attempt to connect if the client is not connected.

I then attach the database to `req.db` and client to `req.mongoClient`. The client can be reused later in our session middleware.

It is not recommended to hard-code your MongoDB URI or any other secure variable. I'm getting it via `process.env`.

#### Session middleware

Session is one of the crux elements in our project. In ExpressJS, we can use `express-session`. In Next.js, a session middleware to use is my [next-session](https://www.npmjs.com/package/next-session "next session"). You can go ahead and install it or use/make your middleware.

```bash
npm install next-session
```

Note: At the moment, `next-session` does not have any native session store, but it is compatible with `express-session` session stores by setting `storePromisify` to `true`.

The default store is `MemoryStore`, which is not production-ready. For now, we can use [connect-mongo](https://www.npmjs.com/package/connect-mongo "connect mongo") since we are already using MongoDB:

Creating `middlewares/withSession.js`:

```javascript
import session from 'next-session';
import connectMongo from 'connect-mongo';

const MongoStore = connectMongo(session);

const withSession = handler => session.withSession(handler, {
  store: new MongoStore({ url: process.env.MONGODB_URI }),
});

export default withSession;
```

`MongoStore` also requires `session`. Our `session` middleware is `next-session`.

#### Global middleware

This is my approach to middleware, I'm going to quote it from `next-session`.

> In reality, you would not want to wrap session() around handler in every function. You may run into a situation where the configuration of one session() is different from others. One solution is to create a global middleware.

Simply create `middlewares/withMiddleware.js` and include our other middlewares:

```javascript
import withDatabase from './withDatabase';
import withSession from './withSession';

const middleware = handler => withDatabase(withSession(handler));

export default middleware;
```

It is important to know that the order in `withDatabase(withSession(handler))` is matter. Later we will add a middleware called `withAuthentication` which make use of our database. Thus, we will have `withAuthentication` inside `withDatabase` and inside `withSession` or the database will not be ready and the session will not exist then.

### `components/layout`: The Layout

This part is __optional__. This is where you can include your Header.jsx to have it on every page as long as you wrap the pages with `<Layout>`.

I'm going to add some styles using Next.js `styled-jsx`.

```jsx
import React from 'react';

export default ({ children }) => (
  <>
    <style jsx global>
      {`
        * {
          box-sizing: border-box;
        }
        body {
          color: #4a4a4a;
          background-color: #f8f8f8;
        }
        input {
          width: 100%;
          margin-top: 1rem;
          padding: 1rem;
          border: none;
          background-color: rgba(0, 0, 0, 0.05);
        }
        button {
          color: #ecf0f1;
          margin-top: 1rem;
          background: #009688;
          border: none;
          padding: 1rem;
        }
      `}

    </style>
    { children }
  </>
);
```

### User registration

Let's start with the user registration since we need at least a user to work with.

#### Building the Signup API

Let's say we sign the user up by making a `POST` request to `/api/users` with a name, an email, and a password.

Everything in the `/api` is an API Route, [a new addition to Next.js 9](https://www.hoangvvo.com/blog/explore-next-js-9-api-routes/ "Explore Next.js 9 api routes"). Each has to export a function that take two arguments `req` and `res`. Here is the content for our `users.js`:

```javascript
import isEmail from 'validator/lib/isEmail';
import * as argon2 from 'argon2';
import withMiddleware from '../../middlewares/withMiddleware';

const handler = (req, res) => {
  if (req.method === 'POST') {
    const { email, name, password } = req.body;
    if (!isEmail(email)) {
      return res.send({
        status: 'error',
        message: 'The email you entered is invalid.',
      });
    }
    return req.db.collection('users').countDocuments({ email })
      .then((count) => {
        if (count) {
          return Promise.reject(Error('The email has already been used.'));
        }
        return argon2.hash(password);
      })
      .then(hashedPassword => req.db.collection('users').insertOne({
        email,
        password: hashedPassword,
        name,
      }))
      .then((user) => {
        req.session.userId = user.insertedId;
        res.status(201).send({
          status: 'ok',
          message: 'User signed up successfully',
        });
      })
      .catch(error => res.send({
        status: 'error',
        message: error.toString(),
      }));
  }
  return res.status(405).end();
};

export default withMiddleware(handler);
```

The function validates the email, hash the password, and insert a user into the database. After that, we set `userId` in the session to the newly created object's id.

You can see that I first check that the method is `POST` before proceeding. If it is not, I return status code [405 Method Not Allowed](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405 "405").

If the user is created, I set `req.session.userId` to the created object id. I will explore it later.

Also note that in each case of error, I simply return a [rejection](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/reject "Promise.reject") with Error object. The rejection will be caught at `.catch()`, where I send back a response with the error message via `.toString()` (Error is an [Error object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error "Error object"), thus needed to be converted to a string).

#### `pages/signup.jsx`: The sign up page

In `signup.jsx`, we will have the following content:

```jsx
import React, { useState } from 'react';
import axioswal from 'axioswal';
import Layout from '../components/layout';
import redirectTo from '../lib/redirectTo';

const SignupPage = () => {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleSubmit = (event) => {
    event.preventDefault();
    axioswal
      .post('/api/users', {
        name,
        email,
        password,
      })
      .then((data) => {
        if (data.status === 'ok') {
          redirectTo('/');
        }
      });
  };

  return (
    <Layout>
      <div style={{ margin: '4rem' }}>
        <h1>Sign up</h1>
        <form onSubmit={handleSubmit}>
          <div>
            <input
              type="text"
              placeholder="Your name"
              value={name}
              onChange={e => setName(e.target.value)}
            />
          </div>
          <div>
            <input
              type="email"
              placeholder="Email address"
              value={email}
              onChange={e => setEmail(e.target.value)}
            />
          </div>
          <div>
            <input
              type="password"
              placeholder="Create a password"
              value={password}
              onChange={e => setPassword(e.target.value)}
            />
          </div>
          <button type="submit">
            Sign up
          </button>
        </form>
      </div>
    </Layout>
  );
};

export default SignupPage;
```

Awesome, let's start up our dev server, navigate to `/signup` and check it out. Try sign up with a creative imaginary email and a badass password. 

![full-fledged app with Next.js and MongoDB: Signup 1](//images.ctfassets.net/rgbpfpd0hrww/4I6G1MWjlpWuPDTJ5lfNNl/9e259a7e7b2bdd01cd0241b7b4cbb174/Signup_1.png)

Also, try to:

- Sign up again with the same email.
- Entered some invalid email.

Does it show the error messages? If yes, great! Let's review what you have just done.

In case you are unfamiliar with Hook, I use the React [State Hook](https://reactjs.org/docs/hooks-state.html "React State Hook") `useState`. In every textbox, I set its value to its corresponding `state` variable (`name`, `email`, `password`), and when it changes (`onChange`), I call the the method (`setName`, `setEmail`, `setPassword`) and apply its new value (via `e.target.value`).

Looking at `onSubmit={handleSubmit}`, we can see that the submission (either via a button or Enter) will call the function `handleSubmit`, where we are going to add our login flow. `event.preventDefault()` prevents the form to be submitted (to the current page, which causes the page to rerender).

What `handleSubmit` should do beside `preventDefault()` is to make a `POST` request to `/api/users`. After that, I also need to show an **Error** or **Success** message to the user.

I use my `axioswal`, which make the Axios request, process the response, and show an [sweetalert2](https://sweetalert2.github.io/) dialog based on the response.

```bash
npm install axioswal
```

If you are using a different request library or want to handle it on your own, feel free to do so.

Also, note that I redirect the user if he or she signs up successfully. I use a function defined at `lib/redirectTo.js`:

```javascript
import Router from 'next/router';

export default function redirectTo(destination, { res, status } = {}) {
  if (res) {
    res.writeHead(status || 302, { Location: destination });
    res.end();
  } else if (destination[0] === '/' && destination[1] !== '/') {
    Router.push(destination);
  } else {
    window.location = destination;
  }
}
```
*Taken from a Github snippet, but I forgot where it was. If you know, tell me :(*

### User authentication

Now that we have one user. Let's try to authenticate the user. We actually *did* authenticate the user when he or she signs up:

```javascript
req.session.userId = user.insertedId;
```

Let's see how we can do it in `/login`, where we make a `POST` request to `/api/authenticate`.

#### Building the Authentication API

Let's create `api/authenticate.js`:

```javascript
import * as argon2 from 'argon2';
import withMiddleware from '../../middlewares/withMiddleware';

const handler = (req, res) => {
  if (req.method === 'POST') {
    const { email, password } = req.body;

    return req.db.collection('users').findOne({ email })
      .then((user) => {
        if (user) {
          return argon2.verify(user.password, password)
            .then((result) => {
              if (result) return Promise.resolve(user);
              return Promise.reject(Error('The password you entered is incorrect'));
            });
        }
        return Promise.reject(Error('The email does not exist'));
      })
      .then((user) => {
        req.session.userId = user._id;
        return res.send({
          status: 'ok',
          message: `Welcome back, ${user.name}!`,
        });
      })
      .catch(error => res.send({
        status: 'error',
        message: error.toString(),
      }));
  }
  return res.status(405).end();
};

export default withMiddleware(handler);
```

The logic is simple, we first look up the email by calling `findOne` given the `email` as our query. If the email does not exist we reject and say "The email does not exist". If the email does exist, req.db.collection.findOne returns a **document** which we refer to as `user`.

We then try to match the passwords. I call `argon2.verify` (which hash the received password and compare it with the hashed one) to see if the `password` we get from the request matches the one in the database. If it does not match, `argon2.verify()` returns false. We then reject and say "The password you entered is incorrect".

In reality, however, I may not want to have two distinct responses saying if it is the email or the password that is incorrect. It may allow brute-forcing. We can change it by simply changing each `Error()` object to both say "Your email or password is incorrect".

If it matches, we set `userId` to `req.session` similar to `Signup`, and return a message saying "Welcome back" with the name of the user.

#### `pages/login.jsx`: The login page

Here is our code for `pages/login.jsx`:

```jsx
import React, { useState } from 'react';
import axioswal from 'axioswal';
import Layout from '../components/layout';
import redirectTo from '../lib/redirectTo';

const LoginPage = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleSubmit = (event) => {
    event.preventDefault();
    axioswal
      .post('/api/authenticate', {
        email,
        password,
      })
      .then((data) => {
        if (data.status === 'ok') {
          redirectTo('/');
        }
      });
  };

  return (
    <Layout>
      <div style={{ margin: '4rem' }}>
        <h1>Log in</h1>
        <form onSubmit={handleSubmit}>
          <div>
            <input
              type="email"
              placeholder="Email address"
              value={email}
              onChange={e => setEmail(e.target.value)}
            />
          </div>
          <div>
            <input
              type="password"
              placeholder="Create a password"
              value={password}
              onChange={e => setPassword(e.target.value)}
            />
          </div>
          <button type="submit">
            Log in
          </button>
        </form>
      </div>
    </Layout>
  );
};

export default LoginPage;
```

If you look at it closely, I copy the whole thing from our `signup.jsx`, remove the `name` field, and change the `POST` URL to `api/authenticate`.

The logic is the same. We still redirect the user if he or she logs in successfully.

Start the server and navigate to `/login` to see our result.

Did it work? If it did, awesome! We have managed to get through most of the project.

### Using session to determine user identity

Recall that we set an userId in `req.session`. What we can do is to have a middleware to look it up in our MongoDB database.

![37fk3o](//images.ctfassets.net/rgbpfpd0hrww/2LuHOFvaeAX3JP2h8ZbWnp/813901ac6d5883d349c0243eab13f0d7/37fk3o.jpg)

#### `req.user` middleware

Go ahead and create `middlewares/withAuthentication`:

```javascript
import { ObjectId } from 'mongodb';

const withAuthentication = handler => (req, res) => {
  if (req.session.userId) {
    console.log(req.session.userId);
    return req.db.collection('users').findOne(ObjectId(req.session.userId))
      .then((user) => {
        console.log(user);
        if (user) req.user = user;
        return handler(req, res);
      });
  }
  return handler(req, res);
};

export default withAuthentication;
```

Include it in our `withMiddleware.js`:

```javascript
const middleware = handler => withDatabase(withSession(withAuthentication(handler)));
```

I mentioned that the order is matter because we need `req.session` to be ready then.

If there is a `req.session.userId`, we try to look the id up (making sure convert it into an `ObjectId` first). If there is a user, we attach it to `req`. Why? Because we may reuse this `user` document in other endpoints. Since every endpoint goes through `withAuthentication`, which available via `withMiddleware`, we can determine the user by simply referring `req.user`. This also helps us avoid repetitive codes.

#### Session endpoint to get current user

Let's have an endpoint that fetches the current user. I will have it `/api/session`, but you can call it anything like `/api/user` or `/api/users/me`.

In `/api/session`, put in the following content:

```javascript
import withMiddleware from '../../middlewares/withMiddleware';

const handler = (req, res) => {
  if (req.method === 'GET') {
    if (req.user) {
      const { name, email } = req.user;
      return res.status(200).send({
        status: 'ok',
        data: {
          isLoggedIn: true,
          user: { name, email },
        },
      });
    }
    return res.status(200).send({
      status: 'ok',
      data: {
        isLoggedIn: false,
        user: {},
      },
    });
  }
  return res.status(405).end();
};

export default withMiddleware(handler);
```

In my opinion, it is appropriate to have the endpoint accepts `GET` request for fetching current user information.

As you can see, I first see if a user is logged in simply by checking if `req.user` exists.

If so, I responded saying `isLoggedIn: true` and response with the user's name and email.

If not, I simply response saying `isLoggedIn: false` and an empty user object.

#### User Context

We need to call `GET /api/session` from somewhere and pass the data across React components. I manage to achieve it using React [__Context API__](https://reactjs.org/docs/context.html). 

> Context provides a way to pass data through the component tree without having to pass props down manually at every level.

Yes, just what we need.

Another similar solution would be - I'm sure you heard this a lot - [__Redux__](https://redux.js.org/). However, I believe __Redux__ is overkill and *the simpler the better*.

Let's create `components/UserContext.js`:

```jsx
import React, { createContext, useReducer, useEffect } from 'react';
import axios from 'axios';

const UserContext = createContext();

const reducer = (state, action) => {
  switch (action.type) {
    case 'set':
      return action.data;
    case 'clear':
      return {
        isLoggedIn: false,
        user: {},
      };
    default:
      throw new Error();
  }
};

const UserContextProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, { isLoggedIn: false, user: {} });
  const dispatchProxy = (action) => {
    switch (action.type) {
      case 'fetch':
        return axios.get('/api/session')
          .then(res => ({
            isLoggedIn: res.data.data.isLoggedIn,
            user: res.data.data.user,
          }))
          .then(({ isLoggedIn, user }) => {
            dispatch({
              type: 'set',
              data: { isLoggedIn, user },
            });
          });
      default:
        return dispatch(action);
    }
  };
  useEffect(() => {
    dispatchProxy({ type: 'fetch' });
  }, []);
  return (
    <UserContext.Provider value={{ state, dispatch: dispatchProxy }}>
      { children }
    </UserContext.Provider>
  );
};

const UserContextConsumer = UserContext.Consumer;

export { UserContext, UserContextProvider, UserContextConsumer };
```

Quite a lot going on here. I suggest reading about [Context](https://reactjs.org/docs/context.html "React Context") on React website and heading back here.

We first create a context by calling `createContext()`.

I then create a [Reducer](https://reactjs.org/docs/hooks-reference.html#usereducer) (again, I would suggest do some reading first) using React Hook:

```javascript
const [state, dispatch] = useReducer(reducer, { isLoggedIn: false, user: {} });
```

where `{ isLoggedIn: false, user: {} }` is the default value and `reducer` is as defined above:

```javascript
const reducer = (state, action) => {
  switch (action.type) {
    case 'set':
      return action.data;
    case 'clear':
      return {
        isLoggedIn: false,
        user: {},
      };
    default:
      throw new Error();
  }
};
```

The value that is returned will be set to the `state`.

For example, in the `clear` function, I simply returned empty user object and `isLoggedIn: false`.

Things get a little bit weird at `set`.

Reducer does __not__ support async function so I have to implement a little trick. I have a "proxy" function. All reducers will first go through `dispatchProxy`. 

If the reducer requires an async call such as `fetch` `dispatchProxy()` will do the asynchronous action and call `dispatch()` when the action resolves. In particular, after fetching completed, it `dispatchProxy()` calls `dispatch()` and update the user context. 

If the reducer does not contain an asynchronous call, I simply forward it to the actual `dispatch()`.

Every time I need to fetch the user data (after logging in, or after changing profiles, etc.), I simply call the `dispatch()`, which is available by simply importing the `UserContext`. Also notice `useEffect(() => {}, []);`: I want to fetch in the app first rendering.

Similar to the availability of `reducer`, we can access the `state` by importing `UserContext` too. The `state` will contains all the user information we need.

##### Provider

To use Context on child components, we need a [ContextProvider](https://reactjs.org/docs/context.html#contextprovider 'Context Provider') at the higher-order components.

```jsx
<Provider>
  <Child1 />
  <Child 2>
    <Child 3 />
  </Child 2>
</Provider>
```

Looking at the mapping above, `Child 1`, `Child 2`, `Child 3` will be able to access the Context. The `<Provider>` will have this format:

```
<MyContext.Provider value={/* some value */}>
```

If you look back to the return value of `UserContextProvider` in `UserContext.jsx`, you can see that we achieve that. Therefore, simply import `UserContextProvider` in that high ordered component.

In Next.js, [`_app.js`](https://nextjs.org/docs#custom-app 'Custom App') is the highest component we have access to. Thus, we are going to have our `UserContextProvider` imported there.

Creating `pages/_app.jsx`:

```jsx
import React from 'react';
import App, { Container } from 'next/app';
import { UserContextProvider } from '../components/UserContext';

class MyApp extends App {
  render() {
    const { Component, pageProps } = this.props;
    return (
      <Container>
        <UserContextProvider>
          <Component {...pageProps} />
        </UserContextProvider>
      </Container>
    );
  }
}

export default MyApp;
```

##### Accessing `state` of `UserContext`

Open up our `pages/index.js` and fill in the following:

```jsx
import React, { useContext } from 'react';
import Link from 'next/link';
import { UserContext } from '../components/UserContext';
import Layout from '../components/layout';

const IndexPage = () => {
  const { state: { isLoggedIn, user: { name } } } = useContext(UserContext);

  return (
    <Layout>
      <div>
        <h1>
          Hello,
          {' '}
          {(isLoggedIn ? name : 'stranger.')}
        </h1>

        {(!isLoggedIn ? (
          <>
            <Link href="/login"><div><button>Login</button></div></Link>
            <Link href="/signup"><div><button>Sign up</button></div></Link>
          </>
        ) : <button>Logout</button>)}

      </div>
    </Layout>
  );
};

export default IndexPage;
```

Sorry if `const { state: { isLoggedIn, user: { name } } } = useContext(UserContext);` confuses you as I use some ES6 Syntax. I basically get `isLoggedIn` and `user.name` from the `state` of `UserContext`.

I also write a conditional rendering. If `isLoggedIn === true`, I will render the `name`. Otherwise, I will render `"stranger"`. Also, below the Welcome text, if `isLoggedIn === false`, I also render the two links to **Login** and **Sign up**. Similarly, we render the `Logout` button if `isLoggedIn === true`.

##### Dispatch `fetch` in `UserContext`

Remember that we need to dispatch the `fetch` reducer for everything to work.

Open `pages/login.jsx` and `pages/signup.jsx` and include the `UserContext`. Here is the implementation for `pages/login.jsx`. Try to do the other one on your own.

```javascript
axioswal
      .post('/api/authenticate', {
        email,
        password,
      })
      .then((data) => {
        if (data.status === 'ok') {
          //  Fetch the user data for UserContext here
          dispatch({ type: 'fetch' });
          redirectTo('/');
        }
      });
```

`dispatch({ type: 'fetch' });` will call the `dispatchProxy()`, which make the `GET` request and call `dispatch()` to update the `UserContext`.

### Logout: delete `userId` from `session`

Let's add functionality to the **Logout** button in `index.jsx`:

```jsx
import React, { useContext } from 'react';
import Link from 'next/link';
import axioswal from 'axioswal';
import { UserContext } from '../components/UserContext';
import Layout from '../components/layout';

const IndexPage = () => {
  const { state: { isLoggedIn, user: { name } }, dispatch } = useContext(UserContext);
  const handleLogout = (event) => {
    event.preventDefault();
    axioswal
      .delete('/api/session')
      .then((data) => {
        if (data.status === 'ok') {
          dispatch({ type: 'clear' });
        }
      });
  };
  return (
    <Layout>
      <div>
        <h1>
          Hello,
          {' '}
          {(isLoggedIn ? name : 'stranger.')}
        </h1>

        {(!isLoggedIn ? (
          <>
            <Link href="/login"><div><button>Login</button></div></Link>
            <Link href="/signup"><div><button>Sign up</button></div></Link>
          </>
        ) : <button onClick={handleLogout}>Logout</button>)}

      </div>
    </Layout>
  );
};

export default IndexPage;
```

I import `dispatch` from `UserContext`. Also, I assign a function to the **Logout** button that makes a `DELETE` request to `/api/session`. If the request is a success, we call the `clear` reducer to empty the `UserContext`.

Obvious we need to add a DELETE request handler in `api/session.js`:

```javascript
import withMiddleware from '../../middlewares/withMiddleware';

const handler = (req, res) => {
  if (req.method === 'GET') {
    if (req.user) {
      const { name, email } = req.user;
      return res.status(200).send({
        status: 'ok',
        data: {
          isLoggedIn: true,
          user: { name, email },
        },
      });
    }
    return res.status(200).send({
      status: 'ok',
      data: {
        isLoggedIn: false,
        user: {},
      },
    });
  }
  if (req.method === 'DELETE') {
    delete req.session.userId;
    return res.status(200).send({
      status: 'ok',
      data: {
        isLoggedIn: false,
        message: 'You have been logged out.',
      },
    });
  }
  return res.status(405).end();
};

export default withMiddleware(handler);
```

Just like we set `userId` to the session when we log in. All I have to do is to delete the `userId` from the session to log out.

## Conclusion

Alright, let's run our app and test it out. This will be the first step in building a full-fledged app using **Next.js** and **MongoDB**.

I hope this can be a boilerplate to launch your next great app. Again, check out the repository [here](https://github.com/hoangvvo/nextjs-mongodb-app "nextjs mongodb app"). I do accept feature requests. What features should I add next time? Feel free to create an issue and tell me.

I'm not so good at writing, so if there is an issue with the article, please help me improve.

Good luck on your next Next.js + MongoDB project!