Koa.js - API RESTful

Để tạo ứng dụng di động, ứng dụng trang đơn, sử dụng lệnh gọi AJAX và cung cấp dữ liệu cho khách hàng, bạn sẽ cần một API. Một kiểu kiến ​​trúc phổ biến về cách cấu trúc và đặt tên cho các API này và các điểm cuối được gọi làREST(Representational Transfer State). HTTP 1.1 được thiết kế để lưu ý các nguyên tắc REST. REST được giới thiệu bởiRoy Fielding vào năm 2000 trong Luận án Fielding giấy của mình.

Các phương thức và URI RESTful cung cấp cho chúng tôi hầu hết mọi thông tin cần thiết để xử lý một yêu cầu. Bảng sau đây tóm tắt cách sử dụng các động từ khác nhau và cách đặt tên các URI. Chúng tôi sẽ tạo một API phim về cuối, vì vậy hãy thảo luận về cách cấu trúc của nó.

phương pháp URI Chi tiết Chức năng
ĐƯỢC /phim An toàn, có thể truy cập được Nhận danh sách tất cả các bộ phim và thông tin chi tiết của chúng
ĐƯỢC / phim / 1234 An toàn, có thể truy cập được Nhận thông tin chi tiết của Movie id 1234
BÀI ĐĂNG /phim N / A Tạo một bộ phim mới với các chi tiết được cung cấp. Phản hồi chứa URI cho tài nguyên mới được tạo này.
ĐẶT / phim / 1234 Idempotent Sửa đổi id phim 1234 (tạo một id nếu nó chưa tồn tại). Phản hồi chứa URI cho tài nguyên mới được tạo này.
XÓA BỎ / phim / 1234 Idempotent Id phim 1234 nên bị xóa, nếu nó tồn tại. Phản hồi phải chứa trạng thái của yêu cầu.
XÓA hoặc ĐẶT /phim không hợp lệ Nên không hợp lệ. DELETE và PUT phải chỉ định tài nguyên nào chúng đang làm việc.

Bây giờ chúng ta hãy tạo API này trong Koa. Chúng tôi sẽ sử dụng JSON làm định dạng dữ liệu truyền tải vì nó dễ làm việc với JavaScript và có vô số lợi ích khác. Thay thế tệp index.js của bạn bằng tệp sau:

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);

Bây giờ chúng ta đã thiết lập xong ứng dụng của mình, hãy tập trung vào việc tạo API. Đầu tiên hãy thiết lập tệp phim.js. Chúng tôi không sử dụng cơ sở dữ liệu để lưu trữ phim mà đang lưu trữ chúng trong bộ nhớ, vì vậy mỗi khi máy chủ khởi động lại, các phim do chúng tôi thêm vào sẽ biến mất. Điều này có thể dễ dàng được bắt chước bằng cách sử dụng cơ sở dữ liệu hoặc tệp (sử dụng mô-đun fs nút).

Nhập koa-router, tạo một Router và xuất nó bằng 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;

Nhận các tuyến đường

Xác định lộ trình GET để tải tất cả các bộ phim.

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

Đó là nó. Để kiểm tra xem điều này có hoạt động tốt hay không, hãy chạy ứng dụng của bạn, sau đó mở thiết bị đầu cuối của bạn và nhập -

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

Bạn sẽ nhận được phản hồi sau -

[{"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}]

Chúng tôi có một lộ trình để lấy tất cả các bộ phim. Bây giờ chúng ta hãy tạo một tuyến đường để lấy một bộ phim cụ thể theo id của nó.

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;
}

Điều này sẽ giúp chúng tôi nhận được các bộ phim theo id mà chúng tôi cung cấp. Để kiểm tra điều này, hãy sử dụng lệnh sau trong thiết bị đầu cuối của bạn.

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

Bạn sẽ nhận được phản hồi là -

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

Nếu bạn truy cập một tuyến đường không hợp lệ, nó sẽ tạo ra lỗi không thể GET, trong khi nếu bạn truy cập một tuyến đường hợp lệ có id không tồn tại, nó sẽ tạo ra lỗi 404.

Chúng tôi đã hoàn thành với các tuyến đường GET. Bây giờ, chúng ta hãy chuyển sang tuyến đường POST.

ĐĂNG lộ trình

Sử dụng tuyến đường sau để xử lý dữ liệu ĐÃ ĐĂNG.

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;
}

Thao tác này sẽ tạo một bộ phim mới và lưu trữ nó trong biến phim. Để kiểm tra lộ trình này, hãy nhập thông tin sau vào thiết bị đầu cuối của bạn:

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

Bạn sẽ nhận được phản hồi sau -

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

Để kiểm tra xem điều này đã được thêm vào đối tượng phim chưa, hãy chạy lại yêu cầu nhận / phim / 105. Bạn sẽ nhận được phản hồi sau -

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

Hãy chuyển sang tạo các tuyến PUT và DELETE.

Tuyến đường PUT

Tuyến PUT gần giống hệt như tuyến POST. Chúng tôi sẽ chỉ định id cho đối tượng sẽ được cập nhật / tạo. Tạo tuyến đường theo cách sau:

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};
      }
   }
}

Tuyến đường này sẽ thực hiện chức năng mà chúng tôi đã chỉ định trong bảng trên. Nó sẽ cập nhật đối tượng với các chi tiết mới nếu nó tồn tại. Nếu nó không tồn tại, nó sẽ tạo một đối tượng mới. Để kiểm tra tuyến đường này, hãy sử dụng lệnh curl sau đây. Điều này sẽ cập nhật một bộ phim hiện có. Để tạo Phim mới, chỉ cần thay đổi id thành id không tồn tại.

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

Phản ứng

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

XÓA tuyến đường

Sử dụng mã sau để tạo một lộ trình xóa.

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."};
   }
}

Kiểm tra tuyến đường giống như cách chúng tôi đã làm cho những tuyến đường khác. Khi xóa thành công (ví dụ: id 105), bạn sẽ nhận được -

{message: "Movie id 105 removed."}

Cuối cùng, tệp phim.js của chúng tôi trông giống như:

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;

Điều này hoàn thành API REST của chúng tôi. Giờ đây, bạn có thể tạo các ứng dụng phức tạp hơn nhiều bằng cách sử dụng phong cách kiến ​​trúc đơn giản này và Koa.