Diseño de API RESTful con NodeJS y Restify

Lo que vas a crear

La API RESTful consta de dos conceptos principales: Recurso, y Representación. El recurso puede ser cualquier objeto asociado con datos, o identificado con un URI (más de un URI puede referirse al mismo recurso), y puede operarse utilizando métodos HTTP. La representación es la forma en que se muestra el recurso. En este tutorial cubriremos información teórica sobre el diseño de la API RESTful e implementaremos una API de aplicación de blogging de ejemplo utilizando NodeJS.

Recurso

Elegir los recursos correctos para una API RESTful es una sección importante del diseño. En primer lugar, debe analizar el dominio de su negocio y luego decidir cuántos y qué tipo de recursos se utilizarán que sean relevantes para su necesidad comercial. Si está diseñando una API de blogs, probablemente utilizará Artículo, Usuario, y Comentario. Esos son los nombres de recursos, y los datos asociados con eso son los recursos en sí mismos:

"title": "Cómo diseñar la API RESTful", "contenido": "El diseño de la API RESTful es un caso muy importante en el mundo del desarrollo de software", "autor": "huseyinbabal", "etiquetas": ["tecnología" , "nodejs", "node-restify"] "category": "NodeJS"

Verbos de recursos

Puede continuar con una operación de recursos una vez que haya decidido los recursos necesarios. La operación aquí se refiere a los métodos HTTP. Por ejemplo, para crear un artículo, puede realizar la siguiente solicitud:

POST / articles HTTP / 1.1 Host: localhost: 3000 Content-Type: application / json "title": "Diseño de API RESTful con Restify", "slug": "restful-api-design-with-restify", "content" : "Pellentesque morbi tristique senectus et netus et malesuada fames ac turpis egestas.", "Autor": "huseyinbabal"

De la misma manera, puede ver un artículo existente emitiendo la siguiente solicitud:

GET / articles / 123456789012 HTTP / 1.1 Host: localhost: 3000 Tipo de contenido: application / json

¿Qué hay de actualizar un artículo existente? Puedo escuchar que estás diciendo:

Puedo hacer otra solicitud POST a / articles / update / 123456789012 con la carga útil.

Tal vez sea preferible, pero la URI es cada vez más compleja. Como dijimos anteriormente, las operaciones pueden referirse a métodos HTTP. Esto significa, indicar el actualizar Operación en el método HTTP en lugar de poner eso en el URI. Por ejemplo:

PUT / articles / 123456789012 HTTP / 1.1 Host: localhost: 3000 Content-Type: application / json "title": "Actualizado cómo diseñar la API RESTful", "content": "El diseño actualizado de la API RESTful es un caso muy importante en el mundo del desarrollo de software. "," author ":" huseyinbabal "," tags ": [" technology "," nodejs "," restify "," one tag más "]" category ":" NodeJS "

Por cierto, en este ejemplo ves etiquetas y campos de categoría. Esos no necesitan ser campos obligatorios. Puedes dejarlos en blanco y configurarlos en el futuro.. 

A veces, es necesario eliminar un artículo cuando está desactualizado. En ese caso puedes usar un BORRAR Solicitud HTTP a / artículos / 123456789012.

Los métodos HTTP son conceptos estándar. Si los usa como una operación, tendrá URI simples, y este tipo de API simple lo ayudará a obtener consumidores felices.

¿Qué pasa si quieres insertar un comentario a un artículo? Puede seleccionar el artículo y agregar un nuevo comentario al artículo seleccionado. Al utilizar esta declaración, puede utilizar la siguiente solicitud:

POST / articles / 123456789012 / comments HTTP / 1.1 Host: localhost: 3000 Content-Type: application / json "text": "Wow! Este es un buen tutorial", "author": "john doe"

La forma de recurso anterior se llama como sub-recurso. Comentario es un sub-recurso de Artículo. los Comentario la carga útil anterior se insertará en la base de datos como un hijo de Artículo. A veces, un URI diferente se refiere al mismo recurso. Por ejemplo, para ver un comentario específico, puede utilizar:

GET / articles / 123456789012 / comments / 123 HTTP / 1.1 Host: localhost: 3000 Tipo de contenido: application / json 

o:

GET / comments / 123456789012 HTTP / 1.1 Host: localhost: 3000 Tipo de contenido: application / json

Versiones

En general, las características de la API cambian con frecuencia para proporcionar nuevas características a los consumidores. En ese caso, pueden existir dos versiones de la misma API al mismo tiempo. Para separar esas dos características, puede usar el control de versiones. Hay dos formas de versionar

  1. Versión en URI: Puede proporcionar el número de versión en el URI. Por ejemplo, /v1.1/articles/123456789012.
  2. Versión en el encabezado: Proporcione el número de versión en el encabezado y nunca cambie el URI.Por ejemplo:
GET / articles / 123456789012 HTTP / 1.1 Host: localhost: 3000 Versión de aceptación: 1.0

En realidad, la versión cambia solo la representación del recurso, no el concepto del recurso. Por lo tanto, no es necesario cambiar la estructura de URI. En v1.1, tal vez se haya agregado un nuevo campo al artículo. Sin embargo, todavía devuelve un artículo. En la segunda opción, el URI es aún simple y los consumidores no necesitan cambiar su URI en las implementaciones del lado del cliente. 

Es importante diseñar una estrategia para situaciones en las que el consumidor no proporciona un número de versión. Puede generar un error cuando no se proporciona la versión, o puede devolver una respuesta utilizando la primera versión. Si usa la última versión estable como predeterminada, los consumidores pueden obtener muchos errores para sus implementaciones del lado del cliente.

Representación

La representación es la forma en que una API muestra el recurso. Cuando llama a un punto final de la API, se le devolverá un recurso. Este recurso puede estar en cualquier formato como XML, JSON, etc. JSON es preferible si está diseñando una nueva API. Sin embargo, si está actualizando una API existente que solía devolver una respuesta XML, puede proporcionar otra versión para una respuesta JSON. 

Es suficiente información teórica sobre el diseño de RESTful API. Veamos el uso de la vida real diseñando e implementando una API de blogs con Restify..

API REST de blogs

Diseño

Para diseñar una API RESTful, necesitamos analizar el dominio de negocios. Entonces podemos definir nuestros recursos. En una API de blogs, necesitamos:

  • Crear, actualizar, eliminar, ver Artículo
  • Crear un comentario para un determinado Artículo, Actualizar, Eliminar, Ver, Comentario
  • Crear, actualizar, eliminar, ver Usuario

En esta API, no cubriré cómo autenticar a un usuario para crear un artículo o comentario. Para la parte de autenticación, puede consultar el tutorial de autenticación basada en token con AngularJS y NodeJS. 

Nuestros nombres de recursos están listos. Las operaciones de recursos son simplemente CRUD. Puede consultar la siguiente tabla para ver un escaparate general de API.

Nombre del recurso Verbos HTTP Métodos HTTP
Artículo crear artículo
actualizar artículo
borrar artículo
ver artículo
POST / artículos con carga útil
PUT / articles / 123 con Payload
ELIMINAR / artículos / 123
GET / artículo / 123
Comentario crear comentario
actualizar Coment
Eliminar comentario
ver comentario
POST / artículos / 123 / comentarios con carga útil
PUT / comments / 123 con Payload
ELIMINAR / COMENTARIOS / 123
GET / comentarios / 123
Usuario crear usuario
actualizar usuario
borrar usuario
ver Usuario
POST / usuarios con carga útil
PUT / usuarios / 123 con carga útil
ELIMINAR / usuarios / 123
GET / usuarios / 123

Configuración del proyecto

En este proyecto utilizaremos NodeJS con Restificar. Los recursos se guardarán en el MongoDB base de datos. En primer lugar, podemos definir los recursos como modelos en Restify..

Artículo

var mongoose = require ("mongoose"); Esquema var = mangosta.Schema; var ArticleSchema = new Schema (title: String, slug: String, content: String, author: type: String, ref: "User"); mongoose.model ('Artículo', ArticleSchema);

Comentario

var mongoose = require ("mongoose"); Esquema var = mangosta.Schema; var CommentSchema = new Schema (text: String, article: type: String, ref: "Article", autor: type: String, ref: "User"); mongoose.model ('Comment', CommentSchema);

Usuario

No habrá ninguna operación para el recurso Usuario. Asumiremos que ya conocemos al usuario actual que podrá operar en artículos o comentarios.

Puedes preguntar de dónde viene este módulo de mangosta. Es el marco ORM más popular para MongoDB escrito como un módulo NodeJS. Este módulo está incluido en el proyecto dentro de otro archivo de configuración.. 

Ahora podemos definir nuestros verbos HTTP para los recursos anteriores. Puedes ver lo siguiente:

var restify = require ('restify'), fs = require ('fs') var controllers = , controllers_path = process.cwd () + '/ app / controllers' fs.readdirSync (controllers_path) .forEach (function (archivo ) if (file.indexOf ('. js')! = -1) controllers [file.split ('.') [0]] = require (controllers_path + '/' + file)) var server = restify.createServer (); server .use (restify.fullResponse ()) .use (restify.bodyParser ()) // Article Start server.post ("/ articles", controllers.article.createArticle) server.put ("/ articles /: id", controllers.article.updateArticle) server.del ("/ articles /: id", controllers.article.deleteArticle) server.get (ruta: "/ articles /: id", versión: "1.0.0", controladores. article.viewArticle) server.get (path: "/ articles /: id", versión: "2.0.0", controllers.article.viewArticle_v2) // Artículo Fin // Comentario Iniciar server.post ("/ comments" , controllers.comment.createComment) server.put ("/ comments /: id", controllers.comment.viewComment) server.del ("/ comments /: id", controllers.comment.deleteComment) server.get ("/ comments /: id ", controllers.comment.viewComment) // Comment End var port = process.env.PORT || 3000; server.listen (puerto, función (err) si (err) console.error (err) else console.log ('La aplicación está lista en:' + puerto)) if (process.env.environment == 'production' ) process.on ('uncaughtException', function (err) console.error (JSON.parse (JSON.stringify (err, ['stack', 'message', 'inner'], 2)))))

En este fragmento de código, primero de todos los archivos de controlador que contienen métodos de controlador se iteran y todos los controladores se inicializan para ejecutar una solicitud específica al URI. Después de eso, se definen los URI para operaciones específicas para las operaciones básicas de CRUD. También hay versiones de una de las operaciones en el artículo. 

Por ejemplo, si declara la versión como 2 en el encabezado de aceptar versión, verArtículo_v2 será ejecutado. verArtículo y verArtículo_v2 ambos hacen el mismo trabajo, mostrando el recurso, pero muestran el recurso del Artículo en un formato diferente, como puede ver en el título campo abajo Finalmente, el servidor se inicia en un puerto específico y se aplican algunas comprobaciones de informe de errores. Podemos proceder con métodos de controlador para operaciones HTTP en recursos..

article.js

var mongoose = require ('mongoose'), Article = mongoose.model ("Article"), ObjectId = mongoose.Types.ObjectId exports.createArticle = function (req, res, next) var ArticleModel = new Article (req.body ); articleModel.save (function (err, article) if (err) res.status (500); res.json (type: false, data: "Ocurrió un error:" + err) else res.json ( type: true, data: article)) exports.viewArticle = function (req, res, next) Article.findById (new ObjectId (req.params.id), function (err, article) if ( err) res.status (500); res.json (type: false, data: "Ocurrió un error:" + err) else if (article) res.json (type: true, data: article ) else res.json (tipo: falso, datos: "Artículo:" + req.params.id + "no encontrado")) exports.viewArticle_v2 = function (req, res, next) Article.findById (nuevo ObjectId (req.params.id), función (err, article) if (err) res.status (500); res.json (type: false, data: "Ocurrió un error:" + err) else if (article) article.title = article.title + "v2" res.json (type: true, data: article) else res.json (type: false, data : "Artículo:" + req.params.id + "no encontrado")) exports.updateArticle = function (req, res, next) var updatedArticleModel = nuevo artículo (req.body); Article.findByIdAndUpdate (nuevo ObjectId (req.params.id), updatedArticleModel, function (err, article) if (err) res.status (500); res.json (type: false, data: "Ocurrió un error: "+ err) else if (article) res.json (type: true, data: article) else res.json (type: false, data:" Article: "+ req.params. id + "no encontrado")) exports.deleteArticle = function (req, res, next) Article.findByIdAndRemove (new Object (req.params.id), function (err, article) if (err ) res.status (500); res.json (type: false, data: "Ocurrió un error:" + err) else res.json (type: true, data: "Article:" + req. params.id + "eliminado con éxito")) 

Puede encontrar una explicación de las operaciones básicas de CRUD en el lado de Mangosta a continuación:

  • crearArtículo: Este es un simple salvar operación en articuloModelo enviado desde el cuerpo de la solicitud. Se puede crear un nuevo modelo pasando el cuerpo de la solicitud como un constructor a un modelo como var articleModel = nuevo artículo (req.body)
  • verArtículo: Para ver los detalles del artículo, se necesita un ID de artículo en el parámetro URL. Encuentra uno con un parámetro de ID es suficiente para devolver el detalle del artículo.
  • actualizarArtículo: La actualización del artículo es una consulta de búsqueda simple y algo de manipulación de datos en el artículo devuelto. Finalmente, el modelo actualizado debe guardarse en la base de datos emitiendo un salvar mando.
  • deleteArticle: findByIdAndRemove es la mejor manera de eliminar un artículo proporcionando la ID del artículo.

Los comandos de Mongoose mencionados anteriormente son simplemente un método de tipo estático a través del objeto Article que también es una referencia del esquema de Mongoose.

comment.js

var mongoose = require ('mongoose'), Comment = mongoose.model ("Comment"), Article = mongoose.model ("Article"), ObjectId = mongoose.Types.ObjectId exports.viewComment = function (req, res)  Article.findOne ("comments._id": nuevo ObjectId (req.params.id), "comments. $": 1, function (err, comment) if (err) res.status (500) ; res.json (type: false, data: "Ocurrió un error:" + err) else if (comment) res.json (type: true, data: new Comment (comment.comments [0]) ) else else res.json (type: false, data: "Comment:" + req.params.id + "no encontrado")) exports.updateComment = function (req, res, next) var updatedCommentModel = new Comment (req.body); console.log (updatedCommentModel) Article.update ("comments._id": new ObjectId (req.params.id), "$ set": "comments. $. text": updatedCommentModel.text, "comments. $ .author ": updatedCommentModel.author, function (err) if (err) res.status (500); res.json (type: false, data:" Ocurrió un error: "+ err) else res.json (type: true, data: "Comment:" + req.params.id + "updated")) exports.deleteComment = function (req, res, next) Article.findOneAndUpdate ( "comments._id": nuevo ObjectId (req.params.id), "$ pull": "comments": "_id": nuevo ObjectId (req.params.id), function (err, article) if (err) res.status (500); res.json (type: false, data: "Ocurrió un error:" + err) else if (article) res.json (type: true, data: article) else res.json (type: false, data: "Comment:" + req.params.id + "no encontrado"))

Cuando realice una solicitud a uno de los URI de recursos, se ejecutará la función relacionada establecida en el controlador. Cada función dentro de los archivos del controlador puede usar el req y res objetos. los comentario recurso aquí es un sub-recurso de Artículo. Todas las operaciones de consulta se realizan a través del modelo del artículo para encontrar un documento secundario y realizar la actualización necesaria. Sin embargo, siempre que intente ver un recurso de Comentarios, verá uno incluso si no hay una colección en MongoDB.  

Otras sugerencias de diseño

  • Seleccione recursos fáciles de entender para proporcionar un uso fácil a los consumidores.
  • Deje que la lógica empresarial sea implementada por los consumidores. Por ejemplo, el recurso del artículo tiene un campo llamado babosa. Los consumidores no necesitan enviar este detalle a la API REST. Esta estrategia de slug debería administrarse en el lado de la API REST para reducir el acoplamiento entre la API y los consumidores. Los consumidores solo necesitan enviar detalles del título, y usted puede generar el slug de acuerdo con las necesidades de su negocio en el lado de REST API.
  • Implementar una capa de autorización para sus puntos finales de API. Los consumidores no autorizados pueden acceder a datos restringidos que pertenecen a otro usuario. En este tutorial, no cubrimos el recurso del usuario, pero puede consultar la autenticación basada en token con AngularJS y NodeJS para obtener más información sobre las autenticaciones de API..
  • Usuario URI en lugar de cadena de consulta. / artículos / 123  (Bueno), / artículos? id = 123 (Malo).
  • No te quedes con el estado; Siempre use entrada / salida instantánea.
  • Use sustantivo para sus recursos. Puedes usar métodos HTTP para operar en recursos.

Finalmente, si diseña una API RESTful siguiendo estas reglas fundamentales, siempre tendrá un sistema flexible, fácil de mantener y fácilmente comprensible..