Koa.js - API RESTful
Para crear aplicaciones móviles, aplicaciones de una sola página, usar llamadas AJAX y proporcionar datos a los clientes, necesitará una API. Un estilo arquitectónico popular de cómo estructurar y nombrar estas API y los puntos finales se llamaREST(Representational Transfer State). HTTP 1.1 se diseñó teniendo en cuenta los principios REST. REST fue introducido porRoy Fielding en 2000 en su artículo Fielding Dissertations.
Los métodos y URI RESTful nos proporcionan casi toda la información que necesitamos para procesar una solicitud. La siguiente tabla resume cómo se deben usar los distintos verbos y cómo se deben nombrar los URI. Crearemos una API de películas hacia el final, así que analicemos cómo se estructurará.
Método | URI | Detalles | Función |
---|---|---|---|
OBTENER | /películas | Seguro, almacenable en caché | Obtiene la lista de todas las películas y sus detalles. |
OBTENER | / peliculas / 1234 | Seguro, almacenable en caché | Obtiene los detalles de Id. De película 1234 |
ENVIAR | /películas | N / A | Crea una nueva película con los detalles proporcionados. La respuesta contiene el URI para este recurso recién creado. |
PONER | / peliculas / 1234 | Idempotente | Modifica la identificación de la película 1234 (crea una si aún no existe). La respuesta contiene el URI para este recurso recién creado. |
ELIMINAR | / peliculas / 1234 | Idempotente | La ID de película 1234 debe eliminarse, si existe. La respuesta debe contener el estado de la solicitud. |
BORRAR o PONER | /películas | Inválido | Debería ser inválido. DELETE y PUT deben especificar en qué recurso están trabajando. |
Ahora creemos esta API en Koa. Usaremos JSON como nuestro formato de datos de transporte, ya que es fácil trabajar con JavaScript y tiene muchos otros beneficios. Reemplace su archivo index.js con lo siguiente:
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);
Ahora que tenemos nuestra aplicación configurada, concentrémonos en crear la API. Primero configure el archivo movies.js. No estamos usando una base de datos para almacenar las películas, sino que las estamos almacenando en la memoria, por lo que cada vez que el servidor reinicia las películas agregadas por nosotros desaparecerán. Esto se puede imitar fácilmente usando una base de datos o un archivo (usando el módulo node fs).
Importe koa-enrutador, cree un enrutador y expórtelo usando 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;
OBTENER Rutas
Defina la ruta GET para obtener todas las películas.
router.get('/', sendMovies);
function *sendMovies(next){
this.body = movies;
yield next;
}
Eso es. Para probar si esto funciona bien, ejecute su aplicación, luego abra su terminal e ingrese -
curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET localhost:3000/movies
Obtendrá la siguiente respuesta:
[{"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}]
Tenemos una ruta para conseguir todas las películas. Ahora creemos una ruta para obtener una película específica por su identificació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;
}
Esto nos dará las películas de acuerdo con la identificación que proporcionemos. Para probar esto, use el siguiente comando en su terminal.
curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET localhost:3000/movies/101
Obtendrá la respuesta como:
{"id":101,"name":"Fight Club","year":1999,"rating":8.1}
Si visita una ruta no válida, producirá un error no se puede obtener, mientras que si visita una ruta válida con una identificación que no existe, producirá un error 404.
Hemos terminado con las rutas GET. Ahora, pasemos a la ruta POST.
Ruta POST
Utilice la siguiente ruta para manejar los datos publicados.
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;
}
Esto creará una nueva película y la almacenará en la variable películas. Para probar esta ruta, ingrese lo siguiente en su terminal:
curl -X POST --data "name = Toy%20story&year = 1995&rating = 8.5"
https://localhost:3000/movies
Obtendrá la siguiente respuesta:
{"message":"New movie created.","location":"/movies/105"}
Para probar si esto se agregó al objeto de películas, ejecute nuevamente la solicitud de obtención para / movies / 105. Obtendrá la siguiente respuesta:
{"id":105,"name":"Toy story","year":"1995","rating":"8.5"}
Pasemos a crear las rutas PUT y DELETE.
PUT Ruta
La ruta PUT es casi exactamente la misma que la ruta POST. Estaremos especificando la identificación del objeto que se actualizará / creará. Cree la ruta de la siguiente manera:
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};
}
}
}
Esta ruta hará la función que especificamos en la tabla anterior. Actualizará el objeto con nuevos detalles si existe. Si no existe, creará un nuevo objeto. Para probar esta ruta, use el siguiente comando curl. Esto actualizará una película existente. Para crear una nueva película, simplemente cambie la identificación a una identificación no existente.
curl -X PUT --data "name = Toy%20story&year = 1995&rating = 8.5"
https://localhost:3000/movies/101
Respuesta
{"message":"Movie id 101 updated.","location":"/movies/101"}
BORRAR Ruta
Utilice el siguiente código para crear una ruta de eliminación.
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."};
}
}
Pruebe la ruta de la misma manera que lo hicimos con los demás. En la eliminación exitosa (por ejemplo, id 105), obtendrá -
{message: "Movie id 105 removed."}
Finalmente, nuestro archivo movies.js se ve así:
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;
Esto completa nuestra API REST. Ahora puede crear aplicaciones mucho más complejas utilizando este estilo arquitectónico simple y Koa.