[:es]
por Gerardo Fanjul
Vamos a desarrollar una pequeña API RESTfull con Node.js. El proyecto quedará adjunto node-rest-shop.zip.
Node.js es un entorno de ejecución para JavaScript construido con el motor de JavaScript V8 de Chrome. Trabaja en tiempo de ejecución, de código abierto, multi-plataforma, que permite a los desarrolladores crear toda clase de herramientas de lado servidor y aplicaciones en JavaScript. La ejecución en tiempo real está pensada para usarse fuera del contexto de un explorador web (es decir, ejecutarse directamente en una computadora o sistema operativo de servidor). Node.js cambia el paradigma de JavaScript, donde el código se alojaba del lado del cliente y solo el web server lo ejecutaba.
Para la explicación de como crear una pequeña aplicación REST vamos a utilizar las siguientes herramientas/tecnologías:
Supertest es una librería para testing unitario de node.js.
Características de NPM
1) El documento donde está toda la información requerida acerca de los paquetes requeridos en la aplicación es package.json. Debe estar en formato JSON, no solo JavaScripts.
2) Si la aplicación va a ser publicada, se debe agregar un nombre y versión en este archivo, por ejemplo:
package.json
1
2
3
4
|
{
«name»: «api-example»,
«version»: «1.0.0»
}
|
Express tiene las siguientes características y/o funcionalidades.
El desarrollo contendrá un módulo javascript llamado server.js en el cual se configura la publicación de la api (sobre node.js) como ser puerto y protocolo (por ejemplo https).
Este será el encargado del despliegue de la app.js la cual recepcionará los pedidos y delegará a quien (router) va a encargarse de tal pedido, además de encargarse de los errores de pedidos no soportados o errores de servidor internos.
Al llegar el pedido hacia alguno de los routers (en nuestro caso products.js) y dependiendo del método invocado, se accederá a la implementación del mismo. Y el cual requerirá de un modelo del esquema en la base de datos para la manipulación de los datos (product.js).
Para crear un proyecto con Node.js ejecutamos el comando npm init, nos pedirá información acerca de nuestra api como el nombre y versión. Este comando nos creará el archivo necesario (package.json) para la ejecución de la app en Node.js.
Luego comenzamos a instalar los frameworks que utilizaremos npm install –save express, el cual nos agregará las librerías de dicho framework a nuestro proyecto.
Yendo directamente a la implementación creamos una API RESTFull y aceptar peticiones /products. En un primer momento solo aceptaremos pedidos get y post sin utilizar base de datos y luego se irá incrementando el código para el uso de datos.
La estructura del proyecto lucirá de la siguiente manera:
Para la recepción de pedidos desarrollamos un módulo app.js el cual utilizará el framework Express.js para delegar el routeo de los pedidos. En este caso recepcionará ‘/products’ además de encargarse de manejar dos tipos de errores: pedidos no soportados e internos.
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
const express = require(‘express’);
const app = express();
const productRoutes = require(‘./api/products/routes/products’);
app.use(‘/products’, productRoutes);
app.use((req, res, next) => {
const error = new Error(‘Not found’);
error.status = 404;
next(error);
})
app.use((error, req, res, next) => {
res.status(error.status || 500);
res.json({
error: {
message: error.message
}
});
});
module.exports = app;
|
Para que esta API quede utilizable y publicada en un servidor necesitamos de un nuevo módulo llamado server.js en el cual se configura puerto y protocolo en donde se desplegará nuestra app.js.
server.js
1
2
3
4
5
6
7
8
|
const http = require(‘http’);
const app = require(‘./app’);
const port = process.env.port || 9000;
const server = http.createServer(app);
server.listen(port);
|
Luego que los pedidos son recepcionados por la app.js, estos son redirigidos al router que atenderá los mismos.
Para atender dichos pedidos creamos un router (products.js) el cual va a ser aceptado por el servidor. En este ejemplo solo aceptará peticiones por métodos GET y POST http y retronará un objeto json con un mensaje.
products.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
const express = require(‘express’);
const router = express.Router();
router.get(‘/’, (req, res, next) => {
res.status(200).json({
message: ‘Capturando el pedido GET sobre /products’
});
});
router.post(‘/’, (req, res, next) => {
res.status(200).json({
message: ‘Capturando el pedido POST sobre /products’
});
});
|
Se puede observar que captura peticiones ‘/’ ya que nuestra API ya procesa los pedidos ‘/products’.
En nuestra aplicación solo se requiere la utilización del framework mongoose y luego conectar con nuestro esquema.
mongodb
1
2
3
|
const mongoose = require(‘mongoose’);
mongoose.connect(‘mongodb://127.0.0.1:27017/TU_ESQUEMA’);
|
Objetos
Como primer paso debemos crear nuestro módulo de asociación entre nuestro código y una colección de datos en nuestro esquema de MongoDB.
product.js
1
2
3
4
5
6
7
8
9
|
const mongoose = require(‘mongoose’);
const productSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: {type:String, require:true},
price: {type:Number, require:true}
});
module.exports = mongoose.model(‘Product’, productSchema);
|
Manipulación de los objetos
Tan solo importando el framework y el módulo creado anteriormente ya estamos habilitados a manipular esta colección en nuestro esquema.
Para ello modificamos el código del router products.js para obtener datos de nuestro esquema. Aquí utilizaremos funciones provistas por mongoose como ‘find’ para buscar datos, ‘select’ para seleccionar los datos que queremos proyectar en nuestra consulta (en este caso solo nombre, precio y id).
Luego ejecutamos la consulta con la función ‘exec’ y para manipular los datos sincrónicamente concatenamos la función ‘then’.
products.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
router.get(‘/’, (req, res, next) => {
Product.find()
.select(‘name price _id’)
.exec()
.then(docs => {
const response = {
count: docs.length,
products: docs.map(doc => {
return {
name: doc.name,
price: doc.price,
_id: doc._id,
request: {
type: ‘GET’,
url: ‘http://localhost:9100/products/’ + doc._id
}
}
})
};
res.status(200).json(response);
|
Testing unitario
En esta API sencilla solo creamos un test del router de productos. Para ello vamos a instalar el framework jest junto con supertest.
products_router.test.js
1
2
3
4
5
6
7
8
9
|
const request = require(‘supertest’);
const app = require(‘../app’);
describe(‘Test para root’, () => {
test(‘Debe responder el método GET’, () => {
return request(app).get(«/products»).then(response => {
expect(response.statusCode).toBe(200)
})
});
})
|
Configuración y ejecución
Para configurar la ejecución del test desde línea de comandos mediante npm test, es necesario modificar una línea en el archivo package.json.
package.json
1
2
|
«scripts»: {
«test»: «jest»
|
Y como resultado del test obtenemos una interfaz como la siguiente:
Para desarrollar test más completos, asincrónicos con esperas, sin utilizar supertest ver referencia [8].
Cambios en tiempo de ejecución
Algo muy esperable en entornos como Node.js es que al momento de modificar código en el desarrollo no sea necesario parar la ejecución del servidor y volver a ejecutarlo.
Para esto existe un paquete llamado nodemon el cual monitorea nuestro proyecto Node.js y nos reinicia el servidor cuando hay un cambio en los archivos del mismo.
Lo podemos instalar con npm install –save-dev nodemon y luego agregarlo en el archivo de package.json bajo el item scripts:
package.json
1
2
|
«scripts»: {
«start»: «nodemon server.js»
|
Ahora para correr el servidor basta con npm start y quedará levantado el servidor que ante cambios en el código se reiniciará automáticamente.
Logging con Morgan
Para loggear en nuestra aplicación Node.js vamos a utilizar Morgan.
Lo podemos instalar con npm install –save-dev morgan. Y para utlizarlo desde la app solo se necesita requerir el paquete.
package.json
1
2
3
|
const morgan = require(‘morgan’);
app.use(morgan(‘dev’));
|
Para probarlo basta con realizar un request a ‘/products’ y podemos ver un ejemplo de dicho pedido.
En esta prueba observamos en la consola el loggeo de nuestras peticiones y la obtención de datos de la base de datos.
logging
1
2
3
4
|
GET /products/5b46238e860a4d2cd8072f9a 404 14.746 ms – 50
GET /products/ 200 6.214 ms – 745
From database { _id: 5b3ced5dca946c1068547b1f }
GET /products/5b3ced5dca946c1068547b1f 200 3.691 ms – 110
|
Buenas prácticas
Comentarios
Referencias
[1] Node.js, https://nodejs.org/es/
[2] MongoDB, https://www.mongodb.com/
[3] Mongoose, http://mongoosejs.com/
[4] Express web framework, https://developer.mozilla.org/es/docs/Learn/Server-side/Express_Nodejs
[5] Express Pros y Cont, http://www.binariks.com/blog/tools/express-js-mobile-app-development-pros-cons-developers/
[6] Estructura del proyecto, https://blog.risingstack.com/node-hero-node-js-project-structure-tutorial/
[7] Jest.js, https://jestjs.io/
[8] Supertest, http://www.albertgao.xyz/2017/05/24/how-to-test-expressjs-with-jest-and-supertest/
[:en]
por Gerardo Fanjul
Vamos a desarrollar una pequeña API RESTfull con Node.js. El proyecto quedará adjunto node-rest-shop.zip.
Node.js es un entorno de ejecución para JavaScript construido con el motor de JavaScript V8 de Chrome. Trabaja en tiempo de ejecución, de código abierto, multi-plataforma, que permite a los desarrolladores crear toda clase de herramientas de lado servidor y aplicaciones en JavaScript. La ejecución en tiempo real está pensada para usarse fuera del contexto de un explorador web (es decir, ejecutarse directamente en una computadora o sistema operativo de servidor). Node.js cambia el paradigma de JavaScript, donde el código se alojaba del lado del cliente y solo el web server lo ejecutaba.
Para la explicación de como crear una pequeña aplicación REST vamos a utilizar las siguientes herramientas/tecnologías:
Supertest es una librería para testing unitario de node.js.
Características de NPM
1) El documento donde está toda la información requerida acerca de los paquetes requeridos en la aplicación es package.json. Debe estar en formato JSON, no solo JavaScripts.
2) Si la aplicación va a ser publicada, se debe agregar un nombre y versión en este archivo, por ejemplo:
package.json
1
2
3
4
|
{
«name»: «api-example»,
«version»: «1.0.0»
}
|
Express tiene las siguientes características y/o funcionalidades.
El desarrollo contendrá un módulo javascript llamado server.js en el cual se configura la publicación de la api (sobre node.js) como ser puerto y protocolo (por ejemplo https).
Este será el encargado del despliegue de la app.js la cual recepcionará los pedidos y delegará a quien (router) va a encargarse de tal pedido, además de encargarse de los errores de pedidos no soportados o errores de servidor internos.
Al llegar el pedido hacia alguno de los routers (en nuestro caso products.js) y dependiendo del método invocado, se accederá a la implementación del mismo. Y el cual requerirá de un modelo del esquema en la base de datos para la manipulación de los datos (product.js).
Para crear un proyecto con Node.js ejecutamos el comando npm init, nos pedirá información acerca de nuestra api como el nombre y versión. Este comando nos creará el archivo necesario (package.json) para la ejecución de la app en Node.js.
Luego comenzamos a instalar los frameworks que utilizaremos npm install –save express, el cual nos agregará las librerías de dicho framework a nuestro proyecto.
Yendo directamente a la implementación creamos una API RESTFull y aceptar peticiones /products. En un primer momento solo aceptaremos pedidos get y post sin utilizar base de datos y luego se irá incrementando el código para el uso de datos.
La estructura del proyecto lucirá de la siguiente manera:
Para la recepción de pedidos desarrollamos un módulo app.js el cual utilizará el framework Express.js para delegar el routeo de los pedidos. En este caso recepcionará ‘/products’ además de encargarse de manejar dos tipos de errores: pedidos no soportados e internos.
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
const express = require(‘express’);
const app = express();
const productRoutes = require(‘./api/products/routes/products’);
app.use(‘/products’, productRoutes);
app.use((req, res, next) => {
const error = new Error(‘Not found’);
error.status = 404;
next(error);
})
app.use((error, req, res, next) => {
res.status(error.status || 500);
res.json({
error: {
message: error.message
}
});
});
module.exports = app;
|
Para que esta API quede utilizable y publicada en un servidor necesitamos de un nuevo módulo llamado server.js en el cual se configura puerto y protocolo en donde se desplegará nuestra app.js.
server.js
1
2
3
4
5
6
7
8
|
const http = require(‘http’);
const app = require(‘./app’);
const port = process.env.port || 9000;
const server = http.createServer(app);
server.listen(port);
|
Luego que los pedidos son recepcionados por la app.js, estos son redirigidos al router que atenderá los mismos.
Para atender dichos pedidos creamos un router (products.js) el cual va a ser aceptado por el servidor. En este ejemplo solo aceptará peticiones por métodos GET y POST http y retronará un objeto json con un mensaje.
products.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
const express = require(‘express’);
const router = express.Router();
router.get(‘/’, (req, res, next) => {
res.status(200).json({
message: ‘Capturando el pedido GET sobre /products’
});
});
router.post(‘/’, (req, res, next) => {
res.status(200).json({
message: ‘Capturando el pedido POST sobre /products’
});
});
|
Se puede observar que captura peticiones ‘/’ ya que nuestra API ya procesa los pedidos ‘/products’.
En nuestra aplicación solo se requiere la utilización del framework mongoose y luego conectar con nuestro esquema.
mongodb
1
2
3
|
const mongoose = require(‘mongoose’);
mongoose.connect(‘mongodb://127.0.0.1:27017/TU_ESQUEMA’);
|
Objetos
Como primer paso debemos crear nuestro módulo de asociación entre nuestro código y una colección de datos en nuestro esquema de MongoDB.
product.js
1
2
3
4
5
6
7
8
9
|
const mongoose = require(‘mongoose’);
const productSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: {type:String, require:true},
price: {type:Number, require:true}
});
module.exports = mongoose.model(‘Product’, productSchema);
|
Manipulación de los objetos
Tan solo importando el framework y el módulo creado anteriormente ya estamos habilitados a manipular esta colección en nuestro esquema.
Para ello modificamos el código del router products.js para obtener datos de nuestro esquema. Aquí utilizaremos funciones provistas por mongoose como ‘find’ para buscar datos, ‘select’ para seleccionar los datos que queremos proyectar en nuestra consulta (en este caso solo nombre, precio y id).
Luego ejecutamos la consulta con la función ‘exec’ y para manipular los datos sincrónicamente concatenamos la función ‘then’.
products.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
router.get(‘/’, (req, res, next) => {
Product.find()
.select(‘name price _id’)
.exec()
.then(docs => {
const response = {
count: docs.length,
products: docs.map(doc => {
return {
name: doc.name,
price: doc.price,
_id: doc._id,
request: {
type: ‘GET’,
url: ‘http://localhost:9100/products/’ + doc._id
}
}
})
};
res.status(200).json(response);
|
Testing unitario
En esta API sencilla solo creamos un test del router de productos. Para ello vamos a instalar el framework jest junto con supertest.
products_router.test.js
1
2
3
4
5
6
7
8
9
|
const request = require(‘supertest’);
const app = require(‘../app’);
describe(‘Test para root’, () => {
test(‘Debe responder el método GET’, () => {
return request(app).get(«/products»).then(response => {
expect(response.statusCode).toBe(200)
})
});
})
|
Configuración y ejecución
Para configurar la ejecución del test desde línea de comandos mediante npm test, es necesario modificar una línea en el archivo package.json.
package.json
1
2
|
«scripts»: {
«test»: «jest»
|
Y como resultado del test obtenemos una interfaz como la siguiente:
Para desarrollar test más completos, asincrónicos con esperas, sin utilizar supertest ver referencia [8].
Cambios en tiempo de ejecución
Algo muy esperable en entornos como Node.js es que al momento de modificar código en el desarrollo no sea necesario parar la ejecución del servidor y volver a ejecutarlo.
Para esto existe un paquete llamado nodemon el cual monitorea nuestro proyecto Node.js y nos reinicia el servidor cuando hay un cambio en los archivos del mismo.
Lo podemos instalar con npm install –save-dev nodemon y luego agregarlo en el archivo de package.json bajo el item scripts:
package.json
1
2
|
«scripts»: {
«start»: «nodemon server.js»
|
Ahora para correr el servidor basta con npm start y quedará levantado el servidor que ante cambios en el código se reiniciará automáticamente.
Logging con Morgan
Para loggear en nuestra aplicación Node.js vamos a utilizar Morgan.
Lo podemos instalar con npm install –save-dev morgan. Y para utlizarlo desde la app solo se necesita requerir el paquete.
package.json
1
2
3
|
const morgan = require(‘morgan’);
app.use(morgan(‘dev’));
|
Para probarlo basta con realizar un request a ‘/products’ y podemos ver un ejemplo de dicho pedido.
En esta prueba observamos en la consola el loggeo de nuestras peticiones y la obtención de datos de la base de datos.
logging
1
2
3
4
|
GET /products/5b46238e860a4d2cd8072f9a 404 14.746 ms – 50
GET /products/ 200 6.214 ms – 745
From database { _id: 5b3ced5dca946c1068547b1f }
GET /products/5b3ced5dca946c1068547b1f 200 3.691 ms – 110
|
Buenas prácticas
Comentarios
Referencias
[1] Node.js, https://nodejs.org/es/
[2] MongoDB, https://www.mongodb.com/
[3] Mongoose, http://mongoosejs.com/
[4] Express web framework, https://developer.mozilla.org/es/docs/Learn/Server-side/Express_Nodejs
[5] Express Pros y Cont, http://www.binariks.com/blog/tools/express-js-mobile-app-development-pros-cons-developers/
[6] Estructura del proyecto, https://blog.risingstack.com/node-hero-node-js-project-structure-tutorial/
[7] Jest.js, https://jestjs.io/
[8] Supertest, http://www.albertgao.xyz/2017/05/24/how-to-test-expressjs-with-jest-and-supertest/
[:]
With a 360° potential, our solutions matrix accompanies the lifecycle of any project, with skills and experience in Development, Design, Q&A, Devops, Operation & Deploy, and Architecture
Discover all postWe are here to help you!
You can leave us your query or recommendation through this form.