Учебники

Koa.js — RESTful API

Для создания мобильных приложений, одностраничных приложений, использования вызовов AJAX и предоставления данных клиентам вам потребуется API. Популярный архитектурный стиль структурирования и именования этих API-интерфейсов и конечных точек называется REST (репрезентативное состояние передачи) . HTTP 1.1 был разработан с учетом принципов REST. REST был представлен Роем ​​Филдингом в 2000 году в своей статье Fielding Dissertations.

RESTful URI и методы предоставляют нам практически всю информацию, необходимую для обработки запроса. В следующей таблице приведены способы использования различных глаголов и имена URI. Мы будем создавать API фильмов к концу, поэтому давайте обсудим, как это будет структурировано.

метод URI подробности функция
ПОЛУЧИТЬ /фильмы Безопасный, кэшируемый Получает список всех фильмов и их детали
ПОЛУЧИТЬ / Фильмы / 1234 Безопасный, кэшируемый Получает подробную информацию о фильме id 1234
СООБЩЕНИЕ /фильмы N / A Создает новый фильм с подробной информацией. Ответ содержит URI для этого вновь созданного ресурса.
ПОЛОЖИЛ / Фильмы / 1234 идемпотент Изменяет идентификатор фильма 1234 (создает его, если он еще не существует). Ответ содержит URI для этого вновь созданного ресурса.
УДАЛЯТЬ / Фильмы / 1234 идемпотент Идентификатор фильма 1234 следует удалить, если он существует. Ответ должен содержать статус запроса.
УДАЛИТЬ или ПОСТАВИТЬ /фильмы Недействительным Должен быть недействительным. DELETE и PUT должны указывать, над каким ресурсом они работают.

Теперь давайте создадим этот API в Koa. Мы будем использовать JSON в качестве нашего транспортного формата данных, так как с ним легко работать в JavaScript и у него есть масса других преимуществ. Замените ваш файл index.js следующим:

index.js

var koa = require('koa');
var router = require('koa-router');
var bodyParser = require('koa-body');

var app = koa();

//Set up body parsing middleware
app.use(bodyParser({
   formidable:{uploadDir: './uploads'},
   multipart: true,
   urlencoded: true
}));

//Require the Router we defined in movies.js
var movies = require('./movies.js');

//Use the Router on the sub route /movies
app.use(movies.routes());

app.listen(3000);

Теперь, когда мы настроили наше приложение, давайте сосредоточимся на создании API. Сначала настройте файл movies.js. Мы не используем базу данных для хранения фильмов, но храним их в памяти, поэтому каждый раз, когда сервер перезапускает, добавленные нами фильмы исчезают. Это можно легко имитировать с помощью базы данных или файла (с помощью модуля node fs).

Импортируйте koa-router, создайте Router и экспортируйте его, используя module.exports.

var Router = require('koa-router');
var router = Router({
  prefix: '/movies'
});  //Prefixed all routes with /movies

var movies = [
   {id: 101, name: "Fight Club", year: 1999, rating: 8.1},
   {id: 102, name: "Inception", year: 2010, rating: 8.7},
   {id: 103, name: "The Dark Knight", year: 2008, rating: 9},
   {id: 104, name: "12 Angry Men", year: 1957, rating: 8.9}
];

//Routes will go here

module.exports = router;

ПОЛУЧИТЬ маршруты

Определите маршрут GET для получения всех фильмов.

router.get('/', sendMovies);
function *sendMovies(next){
   this.body = movies;
   yield next;
}

Вот и все. Чтобы проверить, работает ли это нормально, запустите ваше приложение, затем откройте терминал и введите —

curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET localhost:3000/movies

Вы получите следующий ответ —

[{"id":101,"name":"Fight 
Club","year":1999,"rating":8.1},{"id":102,"name":"Inception","year":2010,"rating":8.7},
{"id":103,"name":"The Dark Knight","year":2008,"rating":9},{"id":104,"name":"12 Angry 
Men","year":1957,"rating":8.9}]

У нас есть маршрут, чтобы получить все фильмы. Теперь давайте создадим маршрут для получения конкретного фильма по его идентификатору.

router.get('/:id([0-9]{3,})', sendMovieWithId);

function *sendMovieWithId(next){
   var ctx = this;
   var currMovie = movies.filter(function(movie){
      if(movie.id == ctx.params.id){
         return true;
      }
   });
   if(currMovie.length == 1){
      this.body = currMovie[0];
   } else {
      this.response.status = 404;//Set status to 404 as movie was not found
      this.body = {message: "Not Found"};
   }
   yield next;
}

Это даст нам фильмы в соответствии с идентификатором, который мы предоставляем. Чтобы проверить это, используйте следующую команду в своем терминале.

curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET localhost:3000/movies/101

Вы получите ответ как —

{"id":101,"name":"Fight Club","year":1999,"rating":8.1}

Если вы посещаете неверный маршрут, он выдаст ошибку not GET, а если вы посетите действительный маршрут с несуществующим идентификатором, он выдаст ошибку 404.

Мы закончили с маршрутами GET. Теперь перейдем к маршруту POST.

POST Route

Используйте следующий маршрут для обработки POST-данных.

router.post('/', addNewMovie);

function *addNewMovie(next){
   //Check if all fields are provided and are valid:
   if(!this.request.body.name || 
      !this.request.body.year.toString().match(/^[0-9]{4}$/g) || 
      !this.request.body.rating.toString().match(/^[0-9]\.[0-9]$/g)){
      
      this.response.status = 400;
      this.body = {message: "Bad Request"};
   } else {
      var newId = movies[movies.length-1].id+1;
      
      movies.push({
         id: newId,
         name: this.request.body.name,
         year: this.request.body.year,
         rating: this.request.body.rating
      });
      this.body = {message: "New movie created.", location: "/movies/" + newId};
   }
   yield next;
}

Это создаст новый фильм и сохранит его в переменной movies. Чтобы проверить этот маршрут, введите в своем терминале следующее:

curl -X POST --data "name = Toy%20story&year = 1995&rating = 8.5" 
https://localhost:3000/movies

Вы получите следующий ответ —

{"message":"New movie created.","location":"/movies/105"}

Чтобы проверить, было ли это добавлено к объекту movies, снова запустите запрос get для / movies / 105. Вы получите следующий ответ —

{"id":105,"name":"Toy story","year":"1995","rating":"8.5"}

Давайте перейдем к созданию маршрутов PUT и DELETE.

PUT Route

Маршрут PUT почти такой же, как маршрут POST. Мы будем указывать идентификатор для объекта, который будет обновлен / создан. Создайте маршрут следующим образом —

router.put('/:id', updateMovieWithId);

function *updateMovieWithId(next){
   //Check if all fields are provided and are valid:
   if(!this.request.body.name || 
      !this.request.body.year.toString().match(/^[0-9]{4}$/g) || 
      !this.request.body.rating.toString().match(/^[0-9]\.[0-9]$/g) ||
      !this.params.id.toString().match(/^[0-9]{3,}$/g)){
      
      this.response.status = 400;
      this.body = {message: "Bad Request"};
   } else {
      //Gets us the index of movie with given id.
      var updateIndex = movies.map(function(movie){
         return movie.id;
      }).indexOf(parseInt(this.params.id));
      
      if(updateIndex === -1){
         //Movie not found, create new movies.push({
            id: this.params.id,
            name: this.request.body.name,
            year: this.request.body.year,
            rating: this.request.body.rating
         });
         this.body = {message: "New movie created.", location: "/movies/" + this.params.id};    
      } else {
         //Update existing movie
         movies[updateIndex] = {
            id: this.params.id,
            name: this.request.body.name,
            year: this.request.body.year,
            rating: this.request.body.rating
         };
         this.body = {message: "Movie id " + this.params.id + " updated.", location: "/movies/" + this.params.id};
      }
   }
}

Этот маршрут будет выполнять функцию, указанную в таблице выше. Он обновит объект новыми деталями, если он существует. Если он не существует, он создаст новый объект. Чтобы проверить этот маршрут, используйте следующую команду curl. Это обновит существующий фильм. Чтобы создать новый фильм, просто измените идентификатор на несуществующий идентификатор.

curl -X PUT --data "name = Toy%20story&year = 1995&rating = 8.5" 
https://localhost:3000/movies/101

отклик

{"message":"Movie id 101 updated.","location":"/movies/101"}

УДАЛИТЬ Маршрут

Используйте следующий код для создания маршрута удаления.

router.delete('/:id', deleteMovieWithId);

function *deleteMovieWithId(next){
   var removeIndex = movies.map(function(movie){
      return movie.id;
   }).indexOf(this.params.id); //Gets us the index of movie with given id.
   
   if(removeIndex === -1){
      this.body = {message: "Not found"};
   } else {
      movies.splice(removeIndex, 1);
      this.body = {message: "Movie id " + this.params.id + " removed."};
   }
}

Протестируйте маршрут так же, как мы это делали для остальных. При успешном удалении (например, идентификатор 105) вы получите —

{message: "Movie id 105 removed."}

Наконец, наш файл movies.js выглядит так:

var Router = require('koa-router');
var router = Router({
   prefix: '/movies'
});  //Prefixed all routes with /movies
var movies = [
   {id: 101, name: "Fight Club", year: 1999, rating: 8.1},
   {id: 102, name: "Inception", year: 2010, rating: 8.7},
   {id: 103, name: "The Dark Knight", year: 2008, rating: 9},
   {id: 104, name: "12 Angry Men", year: 1957, rating: 8.9}
];

//Routes will go here
router.get('/', sendMovies);
router.get('/:id([0-9]{3,})', sendMovieWithId);
router.post('/', addNewMovie);
router.put('/:id', updateMovieWithId);
router.delete('/:id', deleteMovieWithId);

function *deleteMovieWithId(next){
   var removeIndex = movies.map(function(movie){
      return movie.id;
   }).indexOf(this.params.id); //Gets us the index of movie with given id.
   
   if(removeIndex === -1){
      this.body = {message: "Not found"};
   } else {
      movies.splice(removeIndex, 1);
      this.body = {message: "Movie id " + this.params.id + " removed."};
   }
}

function *updateMovieWithId(next) {
   //Check if all fields are provided and are valid:
   if(!this.request.body.name ||
      !this.request.body.year.toString().match(/^[0-9]{4}$/g) ||
      !this.request.body.rating.toString().match(/^[0-9]\.[0-9]$/g) ||
      !this.params.id.toString().match(/^[0-9]{3,}$/g)){
      
      this.response.status = 400;
      this.body = {message: "Bad Request"};
   } else {
      //Gets us the index of movie with given id.
      var updateIndex = movies.map(function(movie){
         return movie.id;
      }).indexOf(parseInt(this.params.id));
      
      if(updateIndex === -1){
         //Movie not found, create new
         movies.push({
            id: this.params.id,
            name: this.request.body.name,
            year: this.request.body.year,
            rating: this.request.body.rating
         });
         this.body = {message: "New movie created.", location: "/movies/" + this.params.id};
      } else {
         //Update existing movie
            movies[updateIndex] = {
            id: this.params.id,
            name: this.request.body.name,
            year: this.request.body.year,
            rating: this.request.body.rating
         };
         this.body = {message: "Movie id " + this.params.id + " updated.", 
            location: "/movies/" + this.params.id};
      }
   }
}

function *addNewMovie(next){
   //Check if all fields are provided and are valid:
   if(!this.request.body.name ||
      !this.request.body.year.toString().match(/^[0-9]{4}$/g) ||
      !this.request.body.rating.toString().match(/^[0-9]\.[0-9]$/g)){
      
      this.response.status = 400;
      this.body = {message: "Bad Request"};
   } else {
      var newId = movies[movies.length-1].id+1;
      
      movies.push({
         id: newId,
         name: this.request.body.name,
         year: this.request.body.year,
         rating: this.request.body.rating
      });
      this.body = {message: "New movie created.", location: "/movies/" + newId};
   }
   yield next;
}
function *sendMovies(next){
   this.body = movies;
   yield next;
}
function *sendMovieWithId(next){
   var ctx = this
   
   var currMovie = movies.filter(function(movie){
      if(movie.id == ctx.params.id){
         return true;
      }
   });
   if(currMovie.length == 1){
      this.body = currMovie[0];
   } else {
      this.response.status = 404;//Set status to 404 as movie was not found
      this.body = {message: "Not Found"};
   }
   yield next;
}
module.exports = router;

Это завершает наш REST API. Теперь вы можете создавать гораздо более сложные приложения, используя этот простой архитектурный стиль и Коа.