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
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
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
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
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
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
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
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
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.
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.
Last updated