equicolor
5/22/2015 - 5:29 PM

Express API with Async/Await

Express API with Async/Await

import express from "express";

/**
 * Takes a route handling function and returns a function
 * that wraps it after first checking that the strings in
 * `reserved` are not part of `req.body`. Used for ensuring
 * create and update requests do not overwrite server-generated
 * values.
 */
function checkReservedParams(routeHandler, ...reserved) {
  return function(req, res, next) {
    for (let reservedKey of reserved) {
      if (req.body[reservedKey]) {
        return res.status(400).json({
          error: `Cannot specify ${reservedKey} as part of request body`
        });
      }
    }

    routeHandler(req, res, next);
  }
}

/**
 * Takes a route handling function and returns
 * a function that wraps it in a `try/catch`. Caught
 * exceptions are forwarded to the `next` handler.
 */
function errorChecking(routeHandler) {
  return async function (req, res, next) {
    try {
      await routeHandler(req, res, next);
    } catch (err) {
      next(err);
    }
  }
}

/**
 * Takes a Sequelize model class and returns an Express
 * router that provides a standard set of RESTful resource
 * routes.
 */
export default function restful(model) {
  async function index(req, res, next) {
    const { offset = 0, limit = 10, orderBy = "createdAt", orderDirection = "ASC" } = req.query;
    const result = await model.findAndCountAll({
      offset, limit,
      order: [ [orderBy, orderDirection] ]
    });
    res.json({
      offset, limit, orderBy, orderDirection,
      count: result.count,
      data: result.rows
    });
  }

  async function create(req, res, next) {
    const instance = await model.create(req.body);
    res.status(201).json({data: instance});
  }

  async function get(req, res, next) {
    const instance = await model.findOne({
      where: { id: req.params.id }
    });
    if (instance) {
      res.json({data: instance});
    } else {
      res.status(404).json({error: "Not found"});
    }
  }

  async function update(req, res, next) {
    const instance = await model.findOne({
      where: { id: req.params.id }
    });
    if (instance) {
      const updated = await instance.updateAttributes(req.body);
      res.json({data: updated});
    } else {
      res.status(404).json({error: "Not found"});
    }
  }

  async function destroy(req, res, next) {
    const instance = await model.findOne({
      where: { id: req.params.id }
    });
    if (instance) {
      await instance.destroy();
      res.status(204).json({});
    } else {
      res.status(404).json({error: "Not found"});
    }
  }

  function notAllowed(req, res) {
    res.status(405).json({error: "Method not allowed"});
  }

  const router = express.Router();

  router.route("/")
    .get(errorChecking(index))
    .post(errorChecking(checkReservedParams(create, "id", "createdAt", "updatedAt")))
    .all(errorChecking(notAllowed));

  router.route("/:id")
    .get(errorChecking(get))
    .put(errorChecking(checkReservedParams(update, "id", "createdAt", "updatedAt")))
    .delete(errorChecking(destroy))
    .all(errorChecking(notAllowed));

  return router;
}