DEV Community

Felipe Leao
Felipe Leao

Posted on • Edited on

10 1 1 1 1

ExpressJS: How to throw custom errors

The problem

  • Throwing readable custom errors is one of the most critical steps in the development of web applications. The communication between services must be clear and straight to the point.

The tool

  • Express middlewares when building NodeJS applications

The Solution:

  • Build an express middleware to throw custom responses error statusCode and messages

  • Our example will utilize a simple API built with ExpressJS and Prisma as an ORM

// app.ts


import express, { Request, Response, NextFunction } from 'express'
import 'express-async-errors'
import { router } from './routes'
import cors from 'cors'

const app = express()
app.use(express.json())
app.use(cors())
app.use(router)

app.use((err: {err: Error, statusCode: number, message: string}, _req: Request, res: Response, _next: NextFunction) => {
  if (err.err && err.err instanceof Error) {
    return res.status(err.statusCode).json({
      message: err.message,
      status: 'error'
    })
  }
  return res.status(500).json({
    status: 'error',
    message: 'Internal server error'
  })
})

export { app }
Enter fullscreen mode Exit fullscreen mode
  • In addition, we can also define how our error response will be displayed to our clients. Here you can choose to show whatever you want and whatever you feel is pertinent for the client to see.

// lib/error

const builder = ({ statusCode, message }: {statusCode: number, message: string}) => {
  return {
    err: new Error(),
    statusCode,
    message
  }
}

export const error = { builder }

Enter fullscreen mode Exit fullscreen mode
  • The object error should be thrown in the following manner

// src/services/userService.ts

import { error } from '../../lib/error'
import prismaClient from '../database/client'
import { IUser } from '../types/IUser'

interface ICreateUserRequest {
  name: string
  email: string
  password: string
}
const create = async ({ name, email, password }: ICreateUserRequest): Promise<IUser> => {
  if (!email) {
    throw error.builder({ statusCode: 422, message: 'Email not provided' })
  }

  if (await prismaClient.user.findFirst({ where: { email } })) {
    throw error.builder({ statusCode: 412, message: 'Email already taken' })
  }

  const user = await prismaClient.user.create({
    data: { name, email, password }
  })

  return user
}

export const userService = {create}

Enter fullscreen mode Exit fullscreen mode

Image of Datadog

Create and maintain end-to-end frontend tests

Learn best practices on creating frontend tests, testing on-premise apps, integrating tests into your CI/CD pipeline, and using Datadog’s testing tunnel.

Download The Guide

Top comments (6)

Collapse
 
crowdozer profile image
crowdozer • Edited

That's a lot like how I do it 😁

I like to create a generic restful error class instead of throwing via function. Easier for me to identify what's going on

// errors.ts
export default class HttpError extends Error {
    constructor(message = '', code = 500) {
        super(message)
        this.code = code
    }

    code: number
}
Enter fullscreen mode Exit fullscreen mode
// error handler
import { Request, Response, NextFunction } from 'express'
import HttpError from 'errors'

export default function ErrorHandler(err: any, req: Request, res: Response, next: NextFunction) {
    const message = err.message || 'An unknown error has occurred'
    const status = err instanceof HttpError ? err.code : 500

    return res.status(status).json({
        message,
        status
    })
}
Enter fullscreen mode Exit fullscreen mode
throw new HttpError('You did something bad!', 400)

// bubble through the application & turns into
{
  "message": "You did something bad!",
  "status": 400
}
Enter fullscreen mode Exit fullscreen mode

Then you can go crazy creating semantically relevant errors... 😮

export class ValidationError extends HttpError {
    constructor(reason: string) {
        super('Validation error: ' + reason, 400)
    }
}

throw new ValidationError("aaaaah")
Enter fullscreen mode Exit fullscreen mode
Collapse
 
felipeleao18 profile image
Felipe Leao

I appreciate your contribution to the post, which is very useful. Both methods can be used, and they are all extremely effective

Collapse
 
brunosoares99 profile image
Bruno Soares

nice!!! It's so good!!!

Collapse
 
felipeleao18 profile image
Felipe Leao

Thank you!!!!!

Collapse
 
pedrolcio_soaresvaz_53 profile image
Pedro Lúcio Soares Vaz

This is what i was looking for, nice post

Collapse
 
felipeleao18 profile image
Felipe Leao

I'm glad to hear that!

Image of Datadog

Create and maintain end-to-end frontend tests

Learn best practices on creating frontend tests, testing on-premise apps, integrating tests into your CI/CD pipeline, and using Datadog’s testing tunnel.

Download The Guide

👋 Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay