Дикий Григорий

Full-stack веб-разработчик

API на Node.JS: Роутинг. Часть 5

Доброго времени суток! Сегодня продолжим работу над нашим приложением. После того, как мы разработали модели, настала пора разобраться с роутингом, так как именно эта часть определит работу нашего приложения и его архитектуру для клиентов. Мы будем придерживаться CRUD: create, read, update, delete. Express предоставляет следующие функции для работы с роутингом:

  • app.all() - данная функция выполняется перед вызовом любой другой из роутинга.
  • app.get() - выполняет GET из HTTP запроса и отдает в качестве ответа данные.
  • app.post() - выполняет метод POST из HTTP и занимается добавлением данных.
  • app.put() - выполняет метод PUT из HTTP и занимается обновлением данных.
  • app.patch() - выполняет метод PATCH из HTTP и занимается также обновлением данных, но его желательно использовать при обновлении атрибутов модели, но не целиком.
  • app.delete() - выполняет метод DELETE из HTTP и занимается удалением данных.

Для лучшего понимания CRUD изменим файл tasks.js из директории routes:

module.exports = app => {
  const Tasks = app.db.models.Tasks;

  app.route("/tasks")
    .all((req, res) => {
      // Middleware
    })
    .get((req, res) => {
      // "/tasks": Get Tasks List
    })
    .post((req, res) => {
      // "/tasks": Add New Task
    });

  app.route("/tasks/:id")
    .all((req, res) => {
      // Middleware
    })
    .get((req, res) => {
      // "/tasks/1": Find a task
    })
    .put((req, res) => {
      // "/tasks/1": Update a tas
    })
    .delete((req, res) => {
      // "/tasks/1": Delete a task
    });
};

Таким образом мы создали базовую структур принципа CRUD, теперь настала пора заполнить данные функции, для начала определим функции app.all():

app.route("/tasks")
    .all((req, res, next) => {
      // Middleware
      delete req.body.id;
      next();
    })
...
app.route("/tasks/:id")
    .all((req, res, next) => {
      // Middleware
      delete req.body.id;
      next();
    })

В данной функции происходит удаление идентификатора запроса во избежание последующих проблем, ведь req.body будет использован для работы с нашими моделями и для получения моделей при помощи Sequelize будет нужен id задачи. Функция next() нужна для вызова следующего middleware.

Теперь нужно определить app.get() и app.post() для роутнига "tasks":

app.route("/tasks")
    .all((req, res, next) => {
      // Middleware
      delete req.body.id;
      next();
    })
    .get((req, res) => {
      // "/tasks": Get Tasks List
      Tasks.findAll({})
        .then(result => res.json(result))
        .catch(error => {
          res.status(412).json({msg: error.message});
        });
    })
    .post((req, res) => {
      // "/tasks": Add New Task
      Tasks.create(req.body)
        .then(result => res.json(result))
        .catch(error => {
          res.status(412).json({msg: error.message});
        });
    });

При GET запросе используются возможности ORM и вызывается функция findAll(), где в качестве параметров передан пустой объект. Таким образом будут возвращены все задачи пользователя. При запросе POST происходит создание новой задачи из объекта req.body, именно поэтому мы и удаляли req.body.id. При успешном завершении возвращается созданный объект или список объектов, а при неудачной возвращается ошибка с кодом. Важно отметить, что при построении API всегда нужно возвращать HTTP статус.

Теперь определим функции для роутинга "/tasks/:id":

app.route("/tasks/:id")
    .all((req, res, next) => {
      // Middleware
      delete req.body.id;
      next();
    })
    .get((req, res) => {
      // "/tasks/1": Find a task
      Tasks.findOne({where: req.params})
        .then(result => {
          if (result) {
            res.json(result);
          } else {
            res.sendStatus(404);
          }
        })
        .catch(error => {
          res.status(412).json({msg: error.message});
        });
    })
    .put((req, res) => {
      // "/tasks/1": Update a task
      Tasks.update(req.body, {where: req.params})
        .then(result => res.sendStatus(204))
        .catch(error => {
          res.status(412).json({msg: error.message});
        });
    })
    .delete((req, res) => {
      // "/tasks/1": Delete a task
      Tasks.destroy({where: req.params})
        .then(result => res.sendStatus(204))
        .catch(error => {
          res.status(412).json({msg: error.message});
        });
    }); 
В GET используется функция findOne, которая возвращает один объект, а в качестве параметров получает объект по которому происходит выборка данных. В PUT используется метод updtae, который принимает в качестве параметров обновляемые данные и шаблон поиска в базе данных и соответсвенно DELETE удаляет данные при помощи метода destroy, где в качестве запроса передается шаблон поиска.

Данные функции можно упростить если использовать модуль body-parser:

$ npm install body-parser@1.15.0 --save
Теперь нужно изменить файл libs/middlewares.js:
import bodyParser from "body-parser";

module.exports = app => {
  app.set("port", 3000);
  app.set("json spaces", 2);

  app.use(bodyParser.json());
  app.use((req, res, next) => {
    delete req.body.id;
    next(); 
  });
};

Данный модуль занимается преобразованием запроса в объект который далее удобно использовать в функциях роутинга. Также мы поместили код функции .all() в этот файл и теперь можно упростить файл tasks.js из директории routes:

module.exports = app => {
  const Tasks = app.db.models.Tasks;

  app.route("/tasks")
    .get((req, res) => {
      // "/tasks": Get Tasks List
      Tasks.findAll({})
        .then(result => res.json(result))
        .catch(error => {
          res.status(412).json({msg: error.message});
        });
    })
    .post((req, res) => {
      // "/tasks": Add New Task
      Tasks.create(req.body)
        .then(result => res.json(result))
        .catch(error => {
          res.status(412).json({msg: error.message});
        });
    });

  app.route("/tasks/:id")
    .get((req, res) => {
      // "/tasks/1": Find a task
      Tasks.findOne({where: req.params})
        .then(result => {
          if (result) {
            res.json(result);
          } else {
            res.sendStatus(404);
          }
        })
        .catch(error => {
          res.status(412).json({msg: error.message});
        });
    })
    .put((req, res) => {
      // "/tasks/1": Update a task
      Tasks.update(req.body, {where: req.params})
        .then(result => res.sendStatus(204))
        .catch(error => {
          res.status(412).json({msg: error.message});
        });
    })
    .delete((req, res) => {
      // "/tasks/1": Delete a task
      Task.destroy({where: req.params})
        .then(result => res.sendStatus(204))
        .catch(error => {
          res.status(412).json({msg: error.message});
        });
    });
};

В следующей статье я разберу как работать с роутингом для модели Users, а также как тестировать API.

Ссылка на репозиторий: https://github.com/dikiigr/nodejs-api