How to Create an API with Strapi by Rebuilding the MDN Express.js Local Library Website
Tags: api, nextjs, strapi, mongodb
Published: 2021-08-09 11:54:54

This tutorial is a rebuild of the [MDN "Local Library" Express (Nodejs) tutorial](https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website) with modern web tools. We develop a web app that might be used to manage the catalog for a local library.

The main goal of this tutorial is to see how easy to create a backend with Strapi for your App in a few minutes from a user-friendly admin panel and let modern frontend tools like NextJS, NuxtJS, GatsbyJS handle the rendering of your content. 

Our final goal is to have a working copy of the [expressjs local library tutorial](https://github.com/mdn/express-locallibrary-tutorial) powered by StrapiJS, NextJS, MongoDB, use-react-form, SWR, and Geist-UI.

# Prerequisites

Note: it's better to create a Github account first and use it to register for other services except for MongoDB. This saves you a lot of time when deploying the App.

For you to be able to follow along with this tutorial, you'll need the following:

- Node.js and npm (yarn) installed on your machine.
- A Github account
- Heroku account for deployment
- MongoDB installed on your local machine, MongoDB Cloud account for when we deploy our App
- Vercel account for deploying our NextJS App
- Basic React knowledge

# What you'll learn

You'll learn how to setup Strapi locally for development, create API endpoints for your content, how to use MongoDB with Strapi and deploy it to Heroku with environment variables, customize default Strapi controllers.

On the frontend part, you'll learn how to use NextJS to create dynamic and static pages for your content, perform CRUD operation, handling forms with use-form-hook, add styling with Geist UI, deploy the frontend to Vercel.


# What you need to know

You’ll need to know how to create Strapi App and connect it to MongoDB locally. The development of our App in 2 stages:

Installing MongoDB on our machine, creating a NextJS App, and make API calls to create, read, update and delete our content from our Strapi backend.


![basic architecture of our application](https://paper-attachments.dropbox.com/s_DBD9C981D2502AA1CAB4318CF43E9DD0A7C59C6EBA8EE35DE425063D82AC532B_1626803304616_app-steps.png)


When you create the Strapi App, by default, all the content types you create are inaccessible to public users. You need to edit permissions to allow non-authenticated users to access your API endpoints.

[NextJS](https://nextjs.org/) has a file-based routing system where each page automatically becomes a route based on its file name. For example, a page at `pages/books` will be located at `/books`, `pages/index.js` at `/`,  and `pages/books/index.js` is equivalent to `pages/books.js`.

The route `/catalog/books/update/[id].js` is a dynamic route. The `id` determines the `Book` that will be on the update page.

# Setup

To install MongoDB on your local machine. Follow the [ultimate guide to MongoDB](https://masteringbackend.com/posts/mongodb-tutorial-the-ultimate-guide) for a step-by-step installation. After installing, run MongoDB and get database info.

```
    // 1
    brew services start mongodb-community@4.4
    
    //2 run the following command to use the mongo shell.
    mongo
    
    //3 To print the name of databases run
    show dbs
```


You will be presented with information such as below depending on the databases you have in your installation.


![](https://paper-attachments.dropbox.com/s_DBD9C981D2502AA1CAB4318CF43E9DD0A7C59C6EBA8EE35DE425063D82AC532B_1626803849910_show-dbs.png)


By default, MongoDB creates three databases. The one we're going to use is "local" to escape extra steps that are not necessary to continue our goal.

Next, install Strapi and Create a new project using the command below:

```
    yarn create Strapi-app Strapi-local-library
```


Choose your installation type: select `Custom` (manual settings), and select your default database client: select `mongo` and complete the questions with the following info.


![create Strapi app](https://paper-attachments.dropbox.com/s_DBD9C981D2502AA1CAB4318CF43E9DD0A7C59C6EBA8EE35DE425063D82AC532B_1626804450043_create-strapi-app.png)


To verify that our App uses the correct database, start Strapi with `yarn develop` or `npm run develop` wait till it finishes building, then back to `mongo` shell and run the following command

```
    > use local
    > show collections
```


Strapi will automatically open a new tap on your web browser on [http://localhost:1337](http://localhost:1337)[/](http://localhost:1337/)
you'll be invited to create a new admin user. 

After logging in, you need to create a `Book`, `Author`, `Genre`, and `BookInstance` collections. The following image describes the relation between our `content types` and the fields that are necessary for each type.


![contents type relations](https://paper-attachments.dropbox.com/s_DBD9C981D2502AA1CAB4318CF43E9DD0A7C59C6EBA8EE35DE425063D82AC532B_1626804706399_Frame_2content-types.png)


Let’s explain the different collections that we have created. We can translate the image like this:

- The `Author` has many `Books`. `Book` has one `Author`
- `Book` has and `belongs` to many `Genres`, `Genre` has and belong to many `Books`
- `Book` has many `BookInstances`, `BookInstance` has one `Book`

This will help us later to define `Relations` in Strapi for our `Content-Types`. To keep the size of the tutorial as low as possible, I'll only cover the creation of the Author and the Book content types, and you can follow the same steps to finish the rest at this stage. 

We have our Strapi App running and connected to a local MongoDB database.

## Creating the Frontend

Create a NextJS app using any of the commands below:

```
    npx create-next-app project-name
    # or
    yarn create next-app project-name
```


When it finishes installing, you will end up with a project with a folder structure similar to the following screenshot.

![next.js files structure](https://paper-attachments.dropbox.com/s_DBD9C981D2502AA1CAB4318CF43E9DD0A7C59C6EBA8EE35DE425063D82AC532B_1626804852274_file-structure.png)


Next, let’s install third-party packages that will help us create our project successfully. Below is the list and description of each library.

- `useSWR` is a React Hooks library for data fetching. We'll need it to fetch data on the client side.
- `luxon` for formatting dates.
- `react-hook-form` for forms handling
    
    yarn add swr luxon react-hook-form

Next, in the root folder, create `config.next.js` file and add to it the following code.

```
    module.exports = {
       async redirects() {
         return [
           {
             source: '/',
             destination: '/catalog',
             permanent: true,
           },
         ]
       },
     }
```


Next, We will create the `env.local` file and add the following variable.

```
    NEXT_PUBLIC_API_ENDPOINT = "<http://localhost:1337>"
```


# Adding content

Before we start working on the front end, let's add some content. in the admin panel, navigate to the `content-types` builder and click `create new collection type` button, enter the Display name `book` as shown below, and hit `Continue`.

![create a collection type](https://paper-attachments.dropbox.com/s_DBD9C981D2502AA1CAB4318CF43E9DD0A7C59C6EBA8EE35DE425063D82AC532B_1626807045160_ct.png)


Select the `Text` field for the book `title` and click `Add` another field

![](https://paper-attachments.dropbox.com/s_DBD9C981D2502AA1CAB4318CF43E9DD0A7C59C6EBA8EE35DE425063D82AC532B_1626807151356_title.png)


Do the same thing for `summary` (long text) and `ISBN` (short text). For the `author` field, we need to create the `Author` content-type first. Follow previous steps to create the `Author`, `Genre`, and `BookInstance` content types and add a `relation` field for the `Book`.


![edit author](https://paper-attachments.dropbox.com/s_DBD9C981D2502AA1CAB4318CF43E9DD0A7C59C6EBA8EE35DE425063D82AC532B_1626807211623_book-author.png)

![](https://paper-attachments.dropbox.com/s_DBD9C981D2502AA1CAB4318CF43E9DD0A7C59C6EBA8EE35DE425063D82AC532B_1626807248986_book-genre.png)

![](https://paper-attachments.dropbox.com/s_DBD9C981D2502AA1CAB4318CF43E9DD0A7C59C6EBA8EE35DE425063D82AC532B_1626807257195_book-bookinstance.png)


This will automatically add the field `Book` to `Author`, `Genre`, and `BookInstance` content-types.
add some Books and Authors of your choice, or you can copy from the [express-local-library tutorial](https://github.com/mdn/express-locallibrary-tutorial/blob/master/populatedb.js).

If we try to access our API endpoint, we'll get the following message:

```
    {
      "statusCode":403,
      "error":"Forbidden",
      "message":"Forbidden"
    }
```


Navigate to the following link `[http://localhost:1337/admin/settings/users-permissions/roles](http://localhost:1337/admin/settings/users-permissions/roles)`, under public > permission > application tap check Select all options for all content types and save.


![](https://paper-attachments.dropbox.com/s_DBD9C981D2502AA1CAB4318CF43E9DD0A7C59C6EBA8EE35DE425063D82AC532B_1626807376022_permission-checked.png)

# Displaying, editing contents

Next, we'll implement our booklist page. This page needs to display a list of all books in the database along with their author, with each book title being a hyperlink to its associated book detail page.


## Book list page:

Create `pages/catalog/books/index.js` file and add the following code to it:

```
    import Head from 'next/head'
    import Link from 'next/link'
    import { Card, Grid } from '@geist-ui/react'
    export default function Books({data, notFound}) {  
      return (
        <div>
          <Head>
            <title>Book list</title>
            <link rel='icon' href='/favicon.ico' />
          </Head>
          <section className="main-section">
            <h1>
             Books
            </h1>
            {notFound ? <div>not found</div> :
            <Grid.Container gap={1}>{
                data.map((book) => {
                   return(
                     <Grid key={book.id}>
                      <Card>
                        <Link style={{ width: '100%'}} href="/catalog/books/details/[id]" as={`/catalog/books/details/${book.id}`} >
                          <a>
                              <h4>{book.title}</h4>
                          </a>
                        </Link>
                        <p>author: {book.author.family_name} {book.author.first_name}</p>
                      </Card>
                     </Grid>
                   )
                })
             }</Grid.Container>
            }
          </section>
        </div>
      )
    }
    
    export async function getServerSideProps(context) {
      const res = await fetch(`${process.env.NEXT_PUBLIC_API_ENDPOINT}/books/?_sort=updated_at:DESC`)
      const data = await res.json()
    
      if (!data) {
        return {
          notFound: true,
        }
      }
    
      return {
        props: {data}, // will be passed to the page component as props
      }
    }
```


We use the method provided by NextJS `getServerSideProps` to fetch a list of books and return it as props to our Books page component as data. Then we iterate through the list of books and render the book `title` and the `Author`.


## About the Head and Link components:
- The `Head` act as the document head tag
- The `Link` is for navigating between routes
- For more details about the `Link` component and `getServerSideProps` method, see the NextJS documentation at [https://](https://nextjs.org/docs/getting-started)[NextJS](https://nextjs.org/docs/getting-started)[.org/docs/getting-started](https://nextjs.org/docs/getting-started)


## Book detail page

The Book detail page needs to display the information for a specific Book (identified using its `id` field value) and information about each associated copy in the library (BookInstance). Wherever we display an `author`, `genre`, or `bookInstance`, these should be linked to the associated detail page for that item.

```
    //pages/catalog/books/details/[id].js
    
    import { Button, Divider, Loading, Modal, Note } from '@geist-ui/react'
    import Link from 'next/link'
    import { useRouter } from 'next/router'
    import useBook from '@/hooks/useBook'
    
    const Book = () => {
     const router = useRouter()
     const { book, isError, isLoading } = useBook(router.query.id)
      return (
        <section className="main-section">
          {
          isError ? "an error occured !" : isLoading ? <Loading /> :
          <div>
             <div>
                <h2>Title:</h2><p>{book.title}</p>
             </div>
             <div>
                <h2>ISBN:</h2><p>{book.ISBN}</p>
             </div>
             <div>
                <h2>Author:</h2> <p>{book.author.family_name} {book.author.first_name}</p>
             </div>
             <div>
                <h2>Summary:</h2><p>{book.summary}</p>
             </div>
             <div>
                <h2>Genre:</h2>
                <div>
                   {
                   book.genres.length > 0 ? book.genres.map(({name, id}) => {
                      return(
                         <div key={id}>
                            <p>{name}</p>
                         </div>
                      )
                   })
                   : 
                   'this book dont belong to any genre'
                   }
                </div>
             </div>
             <div>
                <h2>Copies:</h2>
                <ul>
                   {
                   book.bookinstances.length > 0 ? book.bookinstances.map(({imprint, status, id}) => {
                      return(
                         <li key={id}>
                            <span> {imprint} </span>
                            <span className={status}> [ {status}  ]</span>
                         </li>
                      )
                   })
                   : 
                   'there are no copies of this book in the library'
                   }
                </ul>
             </div>
          </div>
          }
        </section>
      )
    }
    
    export default Book
```


## About the UseRouter and useBook hooks:
- `u``seRouter` hook comes with NextJS. It allows us to access the router object inside any function component in our App.
- From the router object, we can access the query property that contains our book id `router.query.id`, then we use the book id to fetch a specific book using our custom `useBook` hook
- `useBook` hook is a custom hook that receives an `id` and `initialBook` object as parameters and returns a book object matching that `id`.

```
    // hooks/useBook.js
    
    import useSWR from 'swr'
    import Fetcher from '../utils/Fetcher'
    
    function useBook (id, initialBook) {
       const { data, error } = useSWR(`${process.env.NEXT_PUBLIC_API_ENDPOINT}/books/${id}`, Fetcher, { initialData: initialBook })
    
       return {
         book: data,
         isLoading: !error && !data,
         isError: error
       }
     }
     export default useBook
```


`useBook` hook is built on top of SWR, a React Hooks library for data fetching. For more information about how to use it with Next.js, refer to the official docs [https://swr.vercel.app/docs/with-](https://swr.vercel.app/docs/with-nextjs)[NextJS](https://swr.vercel.app/docs/with-nextjs).


## Create book page

For this page we need to get and display available Author and Genre records in our Book form.

```
    import { useState } from 'react'
    import Head from 'next/head'
    import { useRouter } from 'next/router'
    import { Button, Loading, Spacer } from '@geist-ui/react'
    import useAuthors from '@/hooks/useAuthors'
    import useGenres from '@/hooks/useGenres'
    import { useForm } from "react-hook-form"
    
    export default function CreateBook() {
       const router = useRouter()
       const { authors, isError: isAuthorError, isLoading: authorsIsLoading } = useAuthors({initialData: null})
       const { genres, isError: isGenreError, isLoading: genresIsLoading } = useGenres()
       const { register, handleSubmit } = useForm({mode: "onChange"});
    
       async function createBook(data){
          const res = await fetch(
             `${process.env.NEXT_PUBLIC_API_ENDPOINT}/books`,
             {
               body: JSON.stringify({
                title: data.title,
                author: data.author,
                summary: data.summary,
                genres: data.genre,
                ISBN: data.ISBN,
               }),
               headers: {
                 'Content-Type': 'application/json'
               },
               method: 'POST'
             }
          )
          const result = await res.json()
          if(res.ok){
            router.push(`/catalog/books/details/${result.id}`)
          }
       }
      return (
        <div>
          <Head>
            <title>Create new Book</title>
            <link rel="icon" href="/favicon.ico" />
          </Head>
          <section className="main-section">
            <h1>
             New Book
            </h1>
            {
             isAuthorError || isGenreError ? "An error has occurred."
             : authorsIsLoading || genresIsLoading ? <Loading />
             :
            <form id="Book-form" onSubmit={handleSubmit(createBook)}>
               <div>
                <label htmlFor="title">Title</label>
                <input type="text" name="title" id="title" {...register('title')}/>
               </div>
               <Spacer y={1}/>
               <div>
                <label htmlFor="author">Author</label>
                <select type="text" name="author" id="author" {...register('author')}>
                {authors.map((author) => {
                   return(
                      <option key={author.id} value={author.id}>
                         {author.first_name + " " + author.family_name}
                      </option>
                   )
                })}
                </select>
               </div>
               <Spacer y={1}/>
               <div>
                <label htmlFor="summary">Summary</label>
                <textarea name="summary" id="summary" {...register('summary')}/>
               </div>
               <Spacer y={1}/>
               <div>
                 {genres.length > 0 ?
                   genres.map((genre) => {
                     return(
                       <div key={genre.id}>
                          <input
                            type="checkbox"
                            value={genre.id}
                            id={genre.id}
                            {...register("genre")}
                          />
                        <label htmlFor={genre.id}>{genre.name}</label>
                      </div>
                     )
                   })
                  : null
                 }
               </div>
               <Spacer y={1}/>
               <div>
                <label htmlFor="ISBN">ISBN</label>
                <input type="text" name="ISBN" id="ISBN" {...register('ISBN')}/>
                {ISBNError && 
                <div style={{
                  fontSize:"12px",
                  padding:"8px",
                  color: "crimson"}}>
                    book with same ISBN already exist
                </div>}
               </div>
               <Spacer y={2}/>
               <Button htmlType="submit" type="success" ghost>Submit</Button>
            </form>
          }
          </section>
        </div>
      )
    }
```

This page's code is structured as the following:

First, we create the form with the required fields for creating an Author. Then we use the useForm hook to register the fields and handle the form submission. 

Then we fetch the Genres and Authors to pre-populate our inputs (checkbox, select). The last step is to call the function createBook to handle the API call to create a new Book and redirect the user to the book detail page.


## Update Book page

Updating a book is much like that for creating a book, except that we must populate the form with values from the database.

```
    import { useEffect } from 'react'
    import Head from 'next/head'
    import { Button, Loading, Spacer } from '@geist-ui/react'
    import { withRouter } from 'next/router'
    import useGenres from '@/hooks/useGenres'
    import useAuthors from '@/hooks/useAuthors'
    import useBook from '@/hooks/useBook'
    import { useForm } from "react-hook-form"
    
    function UpdateBook({ router, initialBook }) {
      const { id } = router.query
      // fetching book and genres to populate Author field and display all the genres.
      const {genres, isLoading: genresIsLoading, isError: genresIsError} = useGenres()
      const {authors, isLoading: authorsIsLoading, isError: AuthorsIsError} = useAuthors({initialData: null})
      const { book, isError, isLoading } = useBook(router.query.id ? router.query.id : null, initialBook)
    
      // register form fields 
      const { register, handleSubmit, reset } = useForm({mode: "onChange"});
    
      useEffect(() => {
        const bookGenres = book.genres.map((genre) => {
          let ID = genre.id.toString()
          return ID
        })
        reset({
          title: book.title,
          author: book.author.id,
          summary: book.summary,
          ISBN: book.ISBN,
          genre: bookGenres
        });
      }, [reset])
    
      // API Call Update Book
      async function updateBook(data){
        const res = await fetch(
            `${process.env.NEXT_PUBLIC_API_ENDPOINT}/books/${id}`,
            {
              method: 'PUT',
              headers: {
                'Content-Type': 'application/json'
              },
              body: JSON.stringify({
              title: data.title,
              author: data.author,
              summary: data.summary,
              genres: data.genre,
              ISBN: data.ISBN,
              })
            }
        )
        router.push(`/catalog/books/details/${id}`)
      }
      return (
        <div>
          <Head>
            <title>Update Book</title>
            <link rel="icon" href="/favicon.ico" />
          </Head>
          <section className="main-section">
            <h1>
             Update Book
            </h1>
            {
              genresIsError || AuthorsIsError ? "an error occured" : genresIsLoading || authorsIsLoading ? <Loading /> :  
            <form id="Book-update-form" onSubmit={handleSubmit(updateBook)}>
               <div>
                <label htmlFor="title">Title</label>
                <input type="text"  id="title" {...register("title")}/>
               </div>
               <Spacer y={1}/>
               <div>
                <label htmlFor="author">Author</label>
                <select type="text"  id="author" {...register("author")}>
                  {authors.map((author) => {
                    return(
                        <option key={author.id} value={author.id}>
                          {author.first_name + " " + author.family_name}
                        </option>
                    )
                  })}
                </select>
               </div>
               <Spacer y={1}/>
               <div>
                <label htmlFor="summary" >Summary</label>
                <textarea id="summary" {...register("summary")}/>
               </div>
               <Spacer y={1}/>
               <div>
                <label htmlFor="ISBN">ISBN</label>
                <input type="text" id="ISBN" {...register("ISBN")}/>
               </div>
               <Spacer y={1}/>
               <div>
                 {genres.length > 0 ?
                   genres.map((genre) => {
                     return(
                       <div key={genre.id}>
                          <input
                            type="checkbox"
                            value={genre.id}
                            id={genre.id}
                            {...register("genre")}
                          />
                        <label htmlFor={genre.id}>{genre.name}</label>
                      </div>
                     )
                   })
                  : null
                 }
               </div>
               <Spacer y={2}/>
               <Button auto htmlType="submit" type="success" ghost>Submit</Button>
            </form>
            }
          </section>
        </div>
      )
    }
    
    export default withRouter(UpdateBook)
    
    export async function getStaticPaths() {
      const res = await fetch(`${process.env.NEXT_PUBLIC_API_ENDPOINT}/books`)
      const books = await res.json()
      const paths = books.map((book) => ({
        params: { id: book.id.toString() },
      }))
    
      return { paths, fallback: false }
    }
    
    export async function getStaticProps({ params }) {
      const res = await fetch(`${process.env.NEXT_PUBLIC_API_ENDPOINT}/books/${params.id}`)
      const initialBook = await res.json()
      return {
        props: {
          initialBook,
        },
      }
    }
```


This page's code structure is almost the same as for creating a book page, the differences are:

- The route is dynamic
- Because we are using the `getStaticProps` method, the page is pre-rendered with Static Generation (SSG).
- This time we're using the reset method that `useForm` hook gives us to populate our form fields within the `useEffect` hook

**Delete book page**

We'll be adding the delete functionality in the book detail page, open the `pages/catalog/books/details/[id].js` file and update it with the following code

```
    ...
    const Book = () => {
    ...
            const [toggleModal, setToggleModal] = useState(false)
            const handler = () => setToggleModal(true)
               const closeHandler = (event) => {
                  setToggleModal(false)
               }
               async function DeleteBook() {
                  const res = await fetch(`${process.env.NEXT_PUBLIC_API_ENDPOINT}/books/${router.query.id}`,
                  {
                     method:"DELETE",
                     headers: {
                        'Content-Type' : 'application/json'
                     },
                     body: null
                  })
                  setToggleModal(false)
                  router.push(`/catalog/books`)
               }
              return (
                            <section className="main-section">
          {
                  isError ? "an error occured !" : isLoading ? <Loading /> :
                  <div>
                                    ...
                       <Divider />
           <Button style={{marginRight:"1.5vw"}} auto onClick={handler} type="error">Delete book</Button>
           <Link href={`/catalog/books/update/${book.id}`}>
              <a>
                 <Button auto type="default">Update book</Button>
              </a>
           </Link>
           <Modal open={toggleModal} onClose={closeHandler}>
              {book.bookinstances.length > 0 ?
              <>
              <Modal.Title>
                 <Note type="warning">delete the following copies before deleting this book</Note>
              </Modal.Title>
              <Modal.Content>
                 <ul>
                    {book.bookinstances.map((copie) => {
                       return(
                          <li key={copie.id}>{copie.imprint}, #{copie.id}</li>
                       )
                    })
                    }
                 </ul>
              </Modal.Content>
              </>
              :<>
              <Modal.Title>CONFIRM DELETE BOOK ?</Modal.Title>
              <Modal.Subtitle>This action is ireversible</Modal.Subtitle>
              </>
              }
              <Modal.Action passive onClick={() => setToggleModal(false)}>Cancel</Modal.Action>
              <Modal.Action disabled={book.bookinstances.length > 0} onClick={DeleteBook}>Confirm</Modal.Action>
           </Modal>
                    </div>
          }
        </section>
      )
    }
    
    export default Book
```


Here we're adding the delete functionality to the detail page by adding a delete button toggle a modal component. 

In the modal, we'll check if the `Book` has at least one `BookInstance`. We'll prevent the user from deleting this `Book` and showing a list of `BookInstances` that must be deleted before deleting the `Book`. If the Book has no `BookInstances`, we call the `DeleteBook` function when the user confirms.


# Backend customization

For more information about backend customization, I recommend reading the [official Strapi docs link](https://strapi.io/documentation/developer-docs/latest/development/backend-customization.html#policies). Open Strapi App in visual studio code and open the `book` controller file.


![](https://paper-attachments.dropbox.com/s_DBD9C981D2502AA1CAB4318CF43E9DD0A7C59C6EBA8EE35DE425063D82AC532B_1626807969740_strapi_folder.png)


Add the following code and save:

```
    // api/book/controllers/book.js
    
    const { sanitizeEntity } = require('Strapi-utils');
    
    module.exports = {
      async delete (ctx) {
        const { id } = ctx.params;
        let entity = await Strapi.services.book.find({ id });
        if(entity[0].bookinstances.length > 0) {
            return ctx.send({
              message: 'book contain one or more instances'
          }, 406);
        }
        entity = await Strapi.services.book.delete({ id });
        return sanitizeEntity(entity, { model: Strapi.models.book });
      },
    
      async create(ctx) {
        let entity;
        const { ISBN } = ctx.request.body
        entity = await Strapi.services.book.findOne({ ISBN });
        if (entity){
            return ctx.send({
              message: 'book alredy existe'
          }, 406);
        }
        if (ctx.is('multipart')) {
          const { data, files } = parseMultipartData(ctx);
          entity = await Strapi.services.book.create(data, { files });
        } else {
          entity = await Strapi.services.book.create(ctx.request.body);
        }
        return sanitizeEntity(entity, { model: Strapi.models.book });
      },
    };
```


In this file, we're overriding the default delete route by checking if the `Book` has at least one `bookInstance` we respond with a `406 not Acceptable` error and a `message`, else we allow to delete the Book. 

For the create route, we are checking if a book with the same `ISBN` already exists. We respond with a `406 not Acceptable error` and a `message`. Else we allow creating a new book.


# Deployment

## Deploy Strapi app

To deploy Strapi app, we will create and host our MongoDB instance on the cloud.

**Create a database on MongoDB cloud**
Assuming you have a MongoDB account, I recommend following the official tutorial on creating [a New Cluster](https://docs.atlas.mongodb.com/tutorial/create-new-cluster/) on the MongoDB website. After creating a new cluster, navigate to clusters > your cluster name > collections and click on


![](https://paper-attachments.dropbox.com/s_DBD9C981D2502AA1CAB4318CF43E9DD0A7C59C6EBA8EE35DE425063D82AC532B_1626808057180_createDatabase.png)


You can choose any name for your database. After creating the database, under SECURITY > Database access, create a new user and make sure to save the password for later uses, and the next step is to click on connect.

![](https://paper-attachments.dropbox.com/s_DBD9C981D2502AA1CAB4318CF43E9DD0A7C59C6EBA8EE35DE425063D82AC532B_1626808087345_connect.png)


Lastly, choose to connect your application, you'll get the database connection string


![](https://paper-attachments.dropbox.com/s_DBD9C981D2502AA1CAB4318CF43E9DD0A7C59C6EBA8EE35DE425063D82AC532B_1626808154409_connect-info.png)


We will use this string to connect our Strapi App to the database.

**Create a new App on Heroku**

Login to [Heroku](https://dashboard.heroku.com/) and click on the top right corner

![](https://paper-attachments.dropbox.com/s_DBD9C981D2502AA1CAB4318CF43E9DD0A7C59C6EBA8EE35DE425063D82AC532B_1626808344579_heroku-create-app.png)


After creating the App, go to setting > config vars and click Reveal Config Vars
add config vars as follow.


![](https://paper-attachments.dropbox.com/s_DBD9C981D2502AA1CAB4318CF43E9DD0A7C59C6EBA8EE35DE425063D82AC532B_1626808374110_heroku-db-config.png)


All Config Vars are related to the database information. 


- You can extract the DATABASE_HOST variable from the database connection string following the pattern shown in the picture above. 
- DATABASE_NAME is the name of the MongoDB database you created earlier.
- Same for DATABASE_PASSWORD and DATABASE_USERNAME. 

Now we can deploy our Strapi App directly from the Heroku dashboard, go to deploy tap, and choose Github as the following


![](https://paper-attachments.dropbox.com/s_DBD9C981D2502AA1CAB4318CF43E9DD0A7C59C6EBA8EE35DE425063D82AC532B_1626808410884_heroku-github.png)


Choose the right GitHub repo and click on Connect.

![](https://paper-attachments.dropbox.com/s_DBD9C981D2502AA1CAB4318CF43E9DD0A7C59C6EBA8EE35DE425063D82AC532B_1626808448364_heroku-repo.png)


After Heroku completes connecting to the repository, deploy the App.

![](https://paper-attachments.dropbox.com/s_DBD9C981D2502AA1CAB4318CF43E9DD0A7C59C6EBA8EE35DE425063D82AC532B_1626808492234_heroku-deploy.png)


**Deploy NextJS app**

Connect to your [Vercel](https://vercel.com/) account, select projects, tap and click on New Project, then import the repo you want to deploy. You'll be redirected to the project configuration page.


![](https://paper-attachments.dropbox.com/s_DBD9C981D2502AA1CAB4318CF43E9DD0A7C59C6EBA8EE35DE425063D82AC532B_1626808518128_vercel-configure-project.png)


Add the following Environment Variable to your Vercel account.

```    
    VARIABLE_NAME = NEXT_PUBLIC_API_ENDPOINT
    VALUE = https://your-app-name.herokuapp.com
```
    

Next, click the deploy button.

That's the end of this tutorial on Rebuild the MDN express local library website with Strapi and NextJS. 

Source Code:
NextJS - [https://github.com/okuninoshi/next-local-library.git](https://github.com/okuninoshi/next-local-library.git)

Strapi - [https://github.com/okuninoshi/Strapi-local-library.git](https://github.com/okuninoshi/strapi-local-library.git)


# Conclusion

In this tutorial, we learned how to install Strapi with MongoDB locally and customize our API endpoint to our needs. 

We also learned how to create a NextJS app and communicate with our API to perform CRUD operation with NextJS built-in functionality and use environment variables to deploy our Strapi and NextJS application to Heroku and Vercel.

Next, I propose to extend this App by adding Authentication and user registration with the NextAuth package. I recommend [this article](https://strapi.io/blog/Strapi-JWT-Authentication-Next.js) by Osmar Pérez.