1

I'm setting up my first RESTful API with Express & Mongo DB. So far i've been trying to come up with a pattern that if I don't work on the project for a while I can easily pick up again and same goes if someone new were to join the project.

The basic idea is when someone hits an endpoint, if that endpoint requires amends to 1 or more Mongo DB documents then i'll go ahead and fetch that document in Mongo DB and store it in res.locals to be actioned later on in another middleware. This is shown in exports.populateUserById:

exports.populateUserById = async (req,res,next) => {
  const user = await User.findById(req.body.userId);
  res.locals.userToAction = user;
  next();
}

I will then make my amend to that Mongo DB document in a separate middleware like so and reference the res.locals from the previous middleware which I can call .save() on no problem.:

exports.addToTeam = async (req, res, next) => {
  res.locals.userToAction.team = req.body.teamId;
  await res.locals.userToAction.save();
  res.locals.responseData = { success: true }
  next();
}

Finally I go to my last middleware which is shared by all endpoints which just sends the res.JSON:

exports.respond = (req,res) => {
  res.json(res.locals.responseData);
}

My full endpoint would look like this in my routes:

  router.post('/add-user-to-team',
    authController.populateUserById,
    authController.addToTeam,
    authController.respond 
  );

I have found this method to be handy because I can call the populateUserById controller function on many different endpoints then in another controller function edit the document how I need to.

Is this a fairly typical/ acceptable pattern for a restful API with Mongo DB/ Express or am I on the wrong path?

Also worth mentioning. obviously there are more controller functions in between that check for valid MongoDB object id's and catch async errors but have left them out of this example.

Thanks!

1
  • Though I have limited knowledge on REST, the way you named your route does not look RESTful. Instead of /add-user-to-team, I would expect it to be /team/:teamId/user/:userId or user/:userId/team/:teamId/. Also, why do you break up each mongoose operation? Instead why not just nest the operations and pass all the objects you need in the response at the very end (i.e. userToAction and responseData) ? Commented Mar 27, 2018 at 15:34

2 Answers 2

3
+50

Yours is an exactly what I consider as a bad practice.


exports.populateUserById = async (req,res,next) => {
  const user = await User.findById(req.body.userId);
  // ...
}

I have found this method to be handy because I can call the populateUserById controller function on many different endpoints then in another controller function edit the document how I need to.

What if you want to change the name of the request body params for just 1 route some day? Are you going to create another middleware just for that?


ExpressJs middleware is used to divide different use cases, not lines of code.

E.g.

router.get('/foo',
  (req, res, next) => {
    if (!isAdmin) next();
    // Admin logic
  },
  (req, res, next) => {
    // User logic
  }
);

I can see that you use Mongoose too. So, why not create a method or static function in your model to reuse it later?

const userSchema = new mongoose.Schema({
  team: {type: String}
});

userSchema.methods = {
  async addTeam(team) {
    this.team = team;
    await this.save();
  }
}

const User = mongoose.model('User', userSchema);

// ...

router.post('/user/:userId/team', async (req, res, next) {
  const user = User.findById(req.params.userId);
  await user.addTeam(req.body.teamId);
  res.status(200).json(user);
});

It has the same amount of lines of code. It is easier to test, maintain and expand.

Sign up to request clarification or add additional context in comments.

Comments

1

You can call the functions in the same way with small change in route name

router.post('/add/user/team',
  authController.populateUserById,
  authController.addToTeam,
  authController.respond 
);

But you can also follow the express routes middleware approach where you need to pass userId and teamId in route itself

router.post('/user/:userId/:teamId',authController.respond);

router.param('userId', authController.populateUserById);

router.param('teamId', authController.addToTeam);

Here you will get the userId, teamId in req.params.

or

if you wanted to keep the userId and teamId in body itself. You can do it like this also. Though I will prefer the previous approach.

router.post('/add/:user/:team/',authController.respond);
router.param('user', authController.populateUserById);

router.param('team', authController.addToTeam);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.