javascript - Error handling in NodeJS service classes and controllers - Stack Overflow

I am very new to both JavaScript and NodeJS and am struggling to grapple some of the concepts surroundi

I am very new to both JavaScript and NodeJS and am struggling to grapple some of the concepts surrounding async, promises, and others, and especially how to structure the code properly. I am trying to encapsulate some of the functionality in one of my route controllers by extracting it into a Service class. The initial code I have is something like the below example:

import Person from "../models/person.js";
import mongoose from "mongoose";

async function getPerson(req, res) {
    try{
        if(!mongoose.Types.ObjectId.isValid(req.params.id)){
            return res.status(400).send('Invalid ID');
        }

        // Query DB
        const person = await Person.findById(req.params.id);
        if (!person) {
            res.status(404).send('Not found');
        }

        // Verify
        if (person.Age < 18) {
            res.status(403).send('Cannot query for minors');
        }
        
        // Success
        res.status(200).send(person)

    } catch (error) {
        console.error(error);
        res.status(500).send("Error retrieving media");
    }
}

export { getPerson };

This is the controller for a route like "www.projectdomain/person/99" to retrieve the Person data from the person with ID 99. There are a few different failure modes with error codes 400, 404, 403 or 500 depending on the circumstances.

Now, I want to extract some of this into a more general Service class that isn't necessarily tied to a HTTP get request, and without access to the req and res objects. I have the following:

import Person from "../models/person.js";
import mongoose from "mongoose";

class PersonService {
    getPerson(id) {
        if(!mongoose.Types.ObjectId.isValid(id)){
            throw new Error('Invalid ID');
        }

        const person = await Person.findById(id);
        if (!person) {
            throw new Error("Not found");
        }

        // Verify
        if (person.Age < 18) {
            throw new Error('Cannot query for minors');
        }
        
        // Success
        return person;
    }
}

export default PersonService;

I don't know of a better way to throw informed errors that can be translated to a HTTP error code in the outside context. The Controller would now look something like:

import PersonService from "../services/PersonService.js";

async function getPerson(req, res) {
    const personService = new PersonService();
    try{
        const personData = await personService.getPerson(req.params.id);
        res.status(200).send(personData)

    } catch (error) {
        console.error(error);
        res.status(500).send("Error retrieving media");
    }
}

export { getPerson };

The motivation is of course that we might want to reuse the logic for retrieving a person's data from the database in a context where we don't have res and req. However, any errors thrown by the Service class will now get caught by the outside Controller and returned as a status 500. How can I implement Services with proper error handling in NodeJS? How can I abstract some functionality to Service classes without losing the ability to return informed error codes?

I am very new to both JavaScript and NodeJS and am struggling to grapple some of the concepts surrounding async, promises, and others, and especially how to structure the code properly. I am trying to encapsulate some of the functionality in one of my route controllers by extracting it into a Service class. The initial code I have is something like the below example:

import Person from "../models/person.js";
import mongoose from "mongoose";

async function getPerson(req, res) {
    try{
        if(!mongoose.Types.ObjectId.isValid(req.params.id)){
            return res.status(400).send('Invalid ID');
        }

        // Query DB
        const person = await Person.findById(req.params.id);
        if (!person) {
            res.status(404).send('Not found');
        }

        // Verify
        if (person.Age < 18) {
            res.status(403).send('Cannot query for minors');
        }
        
        // Success
        res.status(200).send(person)

    } catch (error) {
        console.error(error);
        res.status(500).send("Error retrieving media");
    }
}

export { getPerson };

This is the controller for a route like "www.projectdomain/person/99" to retrieve the Person data from the person with ID 99. There are a few different failure modes with error codes 400, 404, 403 or 500 depending on the circumstances.

Now, I want to extract some of this into a more general Service class that isn't necessarily tied to a HTTP get request, and without access to the req and res objects. I have the following:

import Person from "../models/person.js";
import mongoose from "mongoose";

class PersonService {
    getPerson(id) {
        if(!mongoose.Types.ObjectId.isValid(id)){
            throw new Error('Invalid ID');
        }

        const person = await Person.findById(id);
        if (!person) {
            throw new Error("Not found");
        }

        // Verify
        if (person.Age < 18) {
            throw new Error('Cannot query for minors');
        }
        
        // Success
        return person;
    }
}

export default PersonService;

I don't know of a better way to throw informed errors that can be translated to a HTTP error code in the outside context. The Controller would now look something like:

import PersonService from "../services/PersonService.js";

async function getPerson(req, res) {
    const personService = new PersonService();
    try{
        const personData = await personService.getPerson(req.params.id);
        res.status(200).send(personData)

    } catch (error) {
        console.error(error);
        res.status(500).send("Error retrieving media");
    }
}

export { getPerson };

The motivation is of course that we might want to reuse the logic for retrieving a person's data from the database in a context where we don't have res and req. However, any errors thrown by the Service class will now get caught by the outside Controller and returned as a status 500. How can I implement Services with proper error handling in NodeJS? How can I abstract some functionality to Service classes without losing the ability to return informed error codes?

Share Improve this question asked Mar 6 at 3:55 heradsinnheradsinn 1151 silver badge10 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 3

First off, welcome to JavaScript and Node.js

Your current setup works, but as you’ve noticed, lumping all errors into a generic 500 response in the controller loses the granularity you had before (like 400, 404, 403).


Step 1: Create Custom Error Classes

You can define custom error classes that carry extra info, like an HTTP status code. This way, your service can throw meaningful errors, and your controller can interpret them. Here’s an example:

// errors.js
class HttpError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.name = this.constructor.name;
  }
}

class BadRequestError extends HttpError {
  constructor(message = "Bad Request") {
    super(message, 400);
  }
}

class NotFoundError extends HttpError {
  constructor(message = "Not Found") {
    super(message, 404);
  }
}

class ForbiddenError extends HttpError {
  constructor(message = "Forbidden") {
    super(message, 403);
  }
}

export { BadRequestError, NotFoundError, ForbiddenError };

These classes inherit from Error, and each one has a default status code tied to it. You can tweak the messages or add more properties if needed.


Step 2: Update Your Service Class

Now, use these custom errors in your PersonService. Also, since you’re working with async operations (like findById), mark the method as async and handle the promise properly. Here’s how it could look:

// services/PersonService.js
import Person from "../models/person.js";
import mongoose from "mongoose";
import { BadRequestError, NotFoundError, ForbiddenError } from "../errors.js";

class PersonService {
  async getPerson(id) { // Note the 'async' here
    if (!mongoose.Types.ObjectId.isValid(id)) {
      throw new BadRequestError("Invalid ID");
    }

    const person = await Person.findById(id);
    if (!person) {
      throw new NotFoundError("Person not found");
    }

    if (person.Age < 18) {
      throw new ForbiddenError("Cannot query for minors");
    }

    return person;
  }
}

export default PersonService;

Step 3: Update Your Controller

In your controller, catch these errors and use their statusCode to send the right response. If an unexpected error slips through (like a database crash), fall back to 500. Here’s the updated version:

// controllers/personController.js
import PersonService from "../services/PersonService.js";

async function getPerson(req, res) {
  const personService = new PersonService();
  try {
    const personData = await personService.getPerson(req.params.id);
    res.status(200).send(personData);
  } catch (error) {
    // Check if it's one of our custom HttpErrors
    if (error.statusCode) {
      res.status(error.statusCode).send(error.message);
    } else {
      // Unexpected errors (e.g., database issues)
      console.error(error);
      res.status(500).send("Internal server error");
    }
  }
}

export { getPerson };

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744997309a4605274.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信