# Creating A Book Listing - Implementation

## 1 - Route Name

`POST /api/v1/books`

Just like we planned, let's add in the route.

*File: src/routes/book.route.js*

```javascript
const express = require('express')
const router = express.Router()

const { getAllBooks, getBookById, createABook } = require('../controllers/book')

router.get('/', getAllBooks)
router.get('/:id', getBookById)

// This is the new route we are adding in
router.post('/', createABook)
```

Let's also create our controller so we can fill in the details later on.

*File: src/controllers/book/createABook.js*

```javascript
const catchException = require('../../utils/catchExceptions')

/**
 * Creates a new book listing.
 */
const createABook = catchException(async (req, res) => {
  // we'll fill in the details after we get each of the other layers ready
})

module.exports = createABook
```

## 2 - Input Request

Very similar to how we implemented our `registerUserRequestDto`, we'll create a `createBookRequestDto` that has the fields *title, description, price, author,* and *datePublished* in it.

*File: src/requests/createBookRequestDto.js*

```javascript
const ApiException = require('../utils/ApiException')

const fields = ['title', 'description', 'price', 'author', 'datePublished']

const createBookRequestDto = (data) => {
  const errors = []
  fields.forEach((field) => {
    if (!(field in data)) {
      errors.push(`This DTO's property is required: ${field}.`)
    }
  })

  if (errors.length > 0) {
    throw new ApiException({
      status: 'error',
      code: 422,
      message: 'Input fields are of not the correct form.',
      data: null,
      errors
    })
  }

  return data
}

module.exports = createBookRequestDto
```

## 3 - Middleware

We are going to be reusing the `isAuthenticated` middleware from before and add it in right before our controller.

*File: src/routes/book.route.js*

```javascript
const express = require('express')
const router = express.Router()

const { createABook } = require('../controllers/book')

const isAuthenticated = require('../middleware/auth.middleware')

// The isAuthenticated middleware is going to protect this route
// from unauthenticated users from accessing it
router.post('/', isAuthenticated, createABook)
```

## 4 - Validation

Let's not forget about the validation layer. Again, this is very similar to the `registerUserValiator` function we created in the previous chapter except it's just going to be for different fields.

*File: src/validators/createBookValidator.js*

```javascript
const Validator = require('validatorjs')
const ApiException = require('../utils/ApiException')

/**
 * @param {*} data {
 *  - title
 *  - description
 *  - price
 *  - author
 *  - datePublished
 * }
 *
 * @returns Validator
 */
const createBookValidator = (data) => {
  const rules = {
    title: 'required',
    description: 'required',
    price: 'required|numeric|min:1',
    author: 'required',
    datePublished: 'required'
  }

  const validator = new Validator(data, rules)

  if (validator.fails()) {
    let errors = []
    for (const field in validator.errors.errors) {
      errors = errors.concat(validator.errors.errors[field])
    }

    throw new ApiException({
      message: 'There were errors with the validation',
      status: 'error',
      code: 400,
      data: null,
      errors
    })
  }

  return validator
}

module.exports = createBookValidator
```

## 5 - Domain

As mentioned in the previous planning section, this will be the model we create.

*File: src/domain/models/book.model.js*

```javascript
const mongoose = require('mongoose')
const Schema = mongoose.Schema

const bookModel = new mongoose.Schema({
  userId: {
    type: Schema.Types.ObjectId,
    ref: 'user'
  },
  title: {
    type: String,
    required: true
  },
  description: {
    type: String
  },
  price: {
    type: Number,
    required: true
  },
  author: {
    type: String
  },
  datePublished: {
    type: String
  },
  createdAt: {
    type: Date,
    default: Date.now
  }
})

module.exports = mongoose.model('book', bookModel)
```

This will be the repository layer on top of that model.

*File: src/domain/repositories/book.repository.js*

```javascript
const Model = require('../models/book.model')

// Saves a book in the database
const create = async (newBook) => {
  const book = new Model(newBook)
  return await book.save()
}

module.exports = {
  create
}
```

Finally, our service layer that will use the `bookRepository` layer.

*File: src/domain/services/book.service.js*

```javascript
const bookRepository = require('../repositories/book.repository')
const ApiException = require('../../utils/ApiException')
const mongoose = require('mongoose')

// Create a book
const createBook = async (book) => {
  return bookRepository.create(book)
}

module.exports = {
  createBook
}
```

## 6 - Events

None.

## 7 - Response

If we put this all together now in the controller, this is what we'll get.

```javascript
const mongoose = require('mongoose')
const globalResponseDto = require('../../responses/globalResponseDto')
const createBookRequestDto = require('../../requests/createBookRequestDto')
const catchException = require('../../utils/catchExceptions')
const bookService = require('../../domain/services/book.service')
const bookResponseDto = require('../../responses/bookResponseDto')
const createBookValidator = require('../../validators/createBookValidator')

/**
 * Creates a new book listing.
 */
const createABook = catchException(async (req, res) => {
  const createBookRequest = createBookRequestDto({
    id: req.session.user.id,
    ...req.body
  })

  createBookValidator(createBookRequest)

  const book = await bookService.createBook({
    userId: mongoose.Types.ObjectId(req.session.user._id),
    ...req.body
  })

  res.status(200).json(
    globalResponseDto({
      status: 'success',
      code: 200,
      message: `Book has successfully been added to the database.`,
      data: bookResponseDto(book),
      errors: null
    })
  )
})

module.exports = createABook
```

The `createBookRequestDto` and the `createBookValidator` will naturally throw an `ApiException` which will yield the correct output response as we stated previously in the planning section of this endpoint.

In case of no errors and we get a successful pass, we will reuse our `bookResponseDto` from which we created previously on the output of the `bookService.create` method we just implemented and return the newly created book in our response.
