RESTful Node.js: A Structured Approach
  • Book Cover
  • About the Author
  • Links and Resources
  • Part I: The Why
    • Foreword
    • Preface
    • Chapter 1: Introduction
      • The Rise of REST and Distributed Systems
      • Problem #1: Structureless Design, Structureless REST
      • The Emergence of JavaScript and Node.js
      • Problem #2: Structureless JavaScript, Structureless Node.js
      • Behold, the Solution: A Structured Approach
      • Summary
  • Part 2: The Theory
    • Chapter 2: REST Origins
      • A Brief History of the Web and the Birth of REST
      • REST vs. HTTP
      • REST - The Abstract Web Architecture
      • HTTP - A Peak at REST's Concrete Implementation
      • What does it mean for an API to be RESTful?
      • Measuring "RESTfulness" with Richardson Maturity Model
      • Pragmatic REST vs Dogmatic REST
      • Summary
    • Chapter 3: RESTful API Design Guidelines and "Best Practices"
      • Theories vs. Principles vs. Guidelines
      • URI Design
      • Method Verbs
      • Status Codes
      • Representational Design
      • Metadata Design
      • Versioning Strategies
      • Security Considerations
      • Documentation
      • Case Study: GitHub
      • Summary
    • Chapter 4: Structured JavaScript Architecture
      • The Monstrous Monolith and Its Downfall
      • Layered/N-Tier Architecture: The Unpopular Proven Way
      • Microservices and Distributed Computing: A Popular Misdirection
      • Summary
    • Chapter 5: The 8 Step Recipe
      • Route Name (URI)
      • Input Request
      • Middleware
      • Validation
      • Domain
      • Events
      • Output Response
      • Test, Refactor, Document
      • Summary
  • Part 3: The Code
    • Chapter 6: Introduction to the Bookstore API
      • The Bookstore API Endpoint Specifications
      • API Design and Code Structure
      • Project Setup
      • Summary
    • Chapter 7: Retrieving Books from our API
      • Retrieving All Books - Planning
      • Retrieving All Books - Implementation
      • Retrieving A Book By ID - Planning
      • Retrieving A Book By ID - Implementation
      • Summary
    • Chapter 8: Adding Authentication to our API
      • Registering the User - Planning
      • Registering the User - Implementation
      • Logging the User In - Planning
      • Logging the User In - Implementation
      • Getting Authenticated User - Planning
      • Getting Authenticated User - Implementation
      • Summary
    • Chapter 9: Adding the Create, Update, and Delete Operations to our API
      • Creating A Book Listing - Planning
      • Creating A Book Listing - Implementation
      • Updating A Book Listing By ID - Planning
      • Updating A Book Listing By ID - Implementation
      • Deleting A Book Listing By ID - Planning
      • Deleting A Book Listing By ID - Implementation
      • Summary
    • Chapter 10: Testing our API
      • Testing the Request
      • Testing the Middleware
      • Testing the Validation
      • Testing the Domain
      • Testing the Event
      • Testing the Response
      • Testing the Controller
      • Integration Test
      • Summary
  • Conclusion
    • Final Words
  • Bonus!
    • Refactoring to HATEOAS
  • Appendix
    • Sources & References
Powered by GitBook
On this page
  • 1 - Route Name
  • 2 - Input Request
  • 3 - Middleware
  • 4 - Validation
  • 5 - Domain
  • 6 - Events
  • 7 - Response
Edit on GitHub
  1. Part 3: The Code
  2. Chapter 8: Adding Authentication to our API

Logging the User In - Implementation

1 - Route Name

Let's add the route and the controller in first.

POST /api/v1/auth/login

File: src/routes/index.js

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

const userRoutes = require('./user.route')
const authRoutes = require('./auth.route')

function getRouter() {
  router.use('/users', userRoutes)
  router.use('/auth', authRoutes) // our new route

  return router
}

module.exports = getRouter

File: src/routes/auth.route.js

const express = require('express')
const router = express.Router()
const { logUserIn } = require('../controllers/auth')

router.post('/login', logUserIn)

module.exports = router

File: src/controllers/auth/logUserIn.js

const catchExceptions = require('../utils/catchExceptions')

const logUserIn = catchExceptions(async (req, res, next) => {
  // Code goes here...
})

2 - Input Request

Next we'll add in the DTO, let's call it loginUserRequestDto.

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

const fields = ['email', 'password']

const loginUserRequestDto = (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: 'loginUserRequestDto failed.',
      data: null,
      errors
    })
  }

  return data
}

module.exports = loginUserRequestDto

3 - Middleware

None.

4 - Validation

Now time for the validator.

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

/**
 * @param {*} data {
 *  - email
 *  - password
 * }
 *
 * @returns Validator
 */
const loginUserValidator = (data) => {
  const rules = {
    email: 'required|email',
    password: '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: validator.errors.errors
    })
  }

  return validator
}

module.exports = loginUserValidator

5 - Domain

For checking if there is a match in our database, we'll use our userModel that was created in the last section.

We'll add a new method called findUserByEmailAndPassword which will simply use our userModel to do a find in our database.

File: src/domain/services/userRepository.js

const UserModel = require('../models/user.model')

/**
 *
 * @param {*} user {
 *  - name
 *  - email
 *  - password
 * }
 *
 * @returns user
 */
const findUserByEmailAndPassword = async (userData) => {
  const foundUser = await UserModel.findOne(userData)
  return foundUser
}

Once we have database query done, we'll add a new method called loginUser and use it in our authService. Notice here that we throw and exception if we do not find any users in the database. This would mean that the client's request has failed.

File: src/domain/services/authService.js

/**
 * @returns user
 */
const loginUser = async (user) => {
  const loginUser = await userRepository.findUserByEmailAndPassword(user)

  if (!loginUser) {
    throw new ApiException({
      status: 'error',
      code: 400,
      message: `Invalid credentials, please try a different email and password combination.`,
      data: null,
      errors: [
        `Invalid credentials, please try a different email and password combination.`
      ]
    })
  }

  return loginUser
}

6 - Events

None.

7 - Response

Now to put everything all together. We'll reuse our userResponseDto from last section and log the user into our session with a simple req.session.user.

/**
 * Logs the user in and set a session for it.
 */
const logUserIn = catchExceptions(async (req, res) => {
  const loginUserRequest = loginUserRequestDto(req.body)

  loginUserValidator(loginUserRequest)

  // if the user's email and password match in our database
  // then set the current session to that user
  const loggedInUser = await authService.loginUser(loginUserRequest)

  // If there we find a user with authService.loginUser, then
  // we'll set the current session to that user
  req.session.user = loggedInUser

  const userDto = userResponseDto(loggedInUser)

  res.status(200).json(
    globalResponseDto({
      status: 'success',
      code: 200,
      message: `The user has successfully logged in.`,
      data: userDto,
      errors: null
    })
  )
})

module.exports = logUserIn
PreviousLogging the User In - PlanningNextGetting Authenticated User - Planning

Last updated 3 years ago

Once again, our code looks nice and clean .

😎