En este tutorial vamos a crear una API REST con un CRUD(Create, Read, Update, Delete) en el cual vamos a poder dentro de una base de datos hacer las siguientes acciones:
Para el desarrollo de este ejercicio es recomendable usar una terminal linux/unix ya que algunos comandos que se usaran para la elaboracion de la API no son compatibles con windows.
Vamos a crear un folder llamado api con el comando mkdir api
, nos movemos a este folder con el comando cd <{path}/api>
en este folder vamos a inicializar nuestro proyecto con el comando npm init -y
(suponiendo que en tu computadora ya tienes instalado node y npm, de no ser asi, instalalos y sigue los pasos), cuando termine de hacer la creacion del proyecto, veremos que en nuestro folder va a aparecer un archivo llamado package.json, en este archivo esta toda la configuracion de nuestro proyecto, tendras algo asi :
{
"name": "api",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
}
Vamos a instalar las dependencias.
{
"name": "api",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^1.19.0",
"express": "^4.17.1",
"mongoose": "^5.9.22"
},
"devDependencies": {
"nodemon": "^2.0.4"
}
}
Ya con todo instalado vamos a empezar, para esto es recomendable modificar los scripts del archivo package, por lo tanto en donde dice “scripts” agregaremos la siguiente linea "dev": "nodemon server.js"
{
"name": "api",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "nodemon server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^1.19.0",
"express": "^4.17.1",
"mongoose": "^5.9.22"
},
"devDependencies": {
"nodemon": "^2.0.4"
}
}
En mi caso borre el script que viene por defecto y deje el que les mencione anteriormente, porque en realidad no uso ese script, lo que hace este script es inicializar nodemon usando como base el archivo server.js. Para poder ejecutar el script crearemos primero en nuestro folder api el archico server.js con el comando touch server.js
en la terminal. despues de crear el archivo, en la terminal escribe npm run dev y obtendras lo siguiente:
Imgur
Para la estructura de este servicio web vamos a crear un folder llamado app, en este folder vamos a tener toda nuestra logica, server.js se ocupara unica y exclusivamente de lanzar el servidor.
El arbol de la logica te debe de quedar de esta manera:
Ten en cuenta el unico que no todos son folders los nombres que tienen un “.*” son archivos, app.js es un archivo que va a contener toda nuestra aplicacion del servidor, que mas adelante lo vamos a lanzar en server.js.
<h3>app.js</h3>const express = require('express');
const bodyParser = require('body-parser');
const app = express();
//Nos permite manejar peticiones y enviar respuesta en formato json
app.use(bodyParser.json());
//De esta manera indicamos que no vamos a recibir peticiones enviadas directamente de un formulario, sino que sera todo enviado en json
app.use(bodyParser.urlencoded({extended: false}))
module.exports = app
De esta manera ya tenemos configurado nuestro servidor.
<h3>config/config.js</h3>module.exports = {
PORT: process.env.PORT || 3000,
DB: process.env.DB || 'mongodb://localhost:27017/api-example'
}
Este archivo config.js tiene dentro un objeto que vamos a exportar y tiene como atributos variables de configuracion.
<h3>config/database.js</h3>const mongoose = require('mongoose');
const CONFIG = require('./config');
module.exports = {
connection: null,
connect: () => {
if (this.connection) returnthis.connection;
return mongoose.connect(CONFIG.DB, {useUnifiedTopology: true,useNewUrlParser: true}).then(connection => {
this.connection = connection;
console.log('Conexion a DB exitosa');
}).catch(err => console.log(err))
}
}
En este archivo database.js tenemos toda la configuracion para que se conecte a la base de datos mongo.
<h3>server.js</h3>const Database = require('./app/config/database');
const CONFIG = require('./app/config/config');
const app = require('./app/app');
Database.connect();
app.listen(CONFIG.PORT, err => {
if (err) returnconsole.log(err)
console.log(`Servidor corriendo en el puerto: ${CONFIG.PORT}`);
})
En este archivo vamos a inicializar nuestro servidor, por esto instanciamos el archivo database.js, config,js y app.js y las guardamos en las constantes Database, CONFIG, app, respectivamente. Ejecutamos entonces, con todo ya listo, el comando npm run dev
y el resultado es el siguiente:
Si tienes problema de conexion con la base de datos debes de iniciar la base de datos con el siguiente comando: sudo mongod
esto te deberia de solucionar el problema de conexion y permitite ejecutar el servidor sin problema.
En este archivo vamos a crear toda la logica de nuestra base de datos en mongoDB.
const mongoose = require('mongoose');
const ProductSchema = new mongoose.Schema({
name: {
type: String,
unique: true,
required: true
},
price: {
type: Number,
required: true
},
category: {
type: String,
required: true,
enum:['Hogar', 'Cocina', 'Higiene']
},
stock: {
type: Number,
default: 10
},
date: {
type: Date,
default: Date.now()
}
});
const Product = mongoose.model('Product', ProductSchema);
module.exports = Product;
Como puedes ver tenemos un objeto dentro de nuestro objeto, esto es porque necesitamos que los atributos principales, tambien tengan atributos, como por ejemplo que tiempo de dato va a ser, o si es requerido o no, tambien si necesitamos que sea unico o no… Hay mas opciones pero por el momento solo vamos a trabajar con las siguientes.
<h3>controllers/ProductController.js</h3>En este archivo vamos a crear toda la logica de las funciones para nuestras rutas
const Product = require('../models/Products');
functionlistall(req, res) {
Product.find({})
.then(products => {
if(products.length) return res.status(200).send({products})
return res.status(204).send({message: 'NO CONTENT'});
}).catch(err => res.status(500).send({err}))
}
functioncreate(req, res) {
let product = new Product(req.body);
product.save()
.then(product =>
res.status(201).send({product})
).catch(err => res.status(500).send({err}))
}
functionshow(req, res) {
if(req.body.error) return res.status(500).send({error});
if(!req.body.products) return res.status(404).send({message: 'Not Found'});
let products = req.body.products;
return res.status(200).send({products});
}
functionupdate(req, res) {
if(req.body.error) return res.status(500).send({error});
if(!req.body.products) return res.status(404).send({message: 'Not Found'});
let product = req.body.products[0];
product = Object.assign(product, req.body);
product.save()
.then(product => res.status(200).send({message: 'Product Updated', product})
).catch(err => res.status(500).send({err}))
}
functiondeleted(req, res) {
if(req.body.error) return res.status(500).send({error});
if(!req.body.products) return res.status(404).send({message: 'Not Found'});
req.body.products[0].remove()
.then(product => {
res.status(200).send({message:'Product removed', product})
}
).catch(err => res.status(500).send({err}));
}
functionfind(req, res, next){
let query = {};
query[req.params.key] = req.params.value
Product.find(query).then(products => {
if(!products.length) return next();
req.body.products = products;
return next();
}).catch(err =>{
req.body.error = err;
next();
})
}
module.exports = {
listall,
show,
create,
update,
deleted,
find,
}
Como puedes ver tenemos aqui nuestro CRUD, donde podremos listar todos los productos con la funcion listall, tambien vamos a poder ver un solo producto con la funcion show, con la funcion create, agregaremos un producto a nuestra BD, la funcion update actualiza nuestro producto en base de datos, deleted elimina un producto y tenemos un middleware llamado find, este se encarga de hacer una revision de existencia del producto antes de llegar a las funciones de show, update o deleted.
<h3>routes/product.js</h3>En este archivo vamos a crear toda la logica del router.
const express = require('express');
const ProductController = require('../controllers/ProductController');
const router = express.Router();
router.get('/', ProductController.listall)
.post('/', ProductController.create)
.get('/:key/:value', ProductController.find, ProductController.show)
.put('/:key/:value', ProductController.find, ProductController.update)
.delete('/:key/:value', ProductController.find, ProductController.deleted)
module.exports = router;
Como puedes ver tenemos un archivo muy limpio el cual lo que hace es direccionar segun el metodo que nos llegue.
<h3>Modificacion app.js</h3>Finalmente y para que el servidor funcione con toda nuestra logica vamos a modificar nuestro archivo app para incluir lo que acabamos de construir
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const Product = require('./routes/product');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.use('/product', Product);
module.exports = app
Con esta modificacion final ya podemos realizar la pruebas en nuestro servidor para ello entonces ejecutamos npm run dev
mkdir <folder_name>
, desde la terminalcd <{path}/folder_name>
.npm init -y
npm i express body-parser mongoose
npm i -D nodemon
"dev": "nodemon server.js"
en la seccion scripts de nuestro archivo package.jsontouch server.js
La actual API REST con Node, Express y Mongoose…
Observa por favor con atención esta aplicación…
La aplicación hace el llamado a diferentes modulos para su correcto funcionamiento.
Tiene un enrutador que se encarga de llamar a la función adecuada dependiendo del tipo de petición que se realice. Estas peticiones hacen referencia a las operaciones CRUD (Create, Read, Update, Delete).
El controlador se encarga de procesar la petición en cada caso (listall, create, show, update, deleted) y obtener una respuesta y en cada caso responde con un mensaje o un objeto JSON, como corresponda.
El archivo de configuración es necesario para establecer variables de entorno, en este caso, el puerto y la dirección de la base de datos a usar.
Por último, el archivo db.js se encarga de la conexión con la base de datos.
También se incluyen otros módulos importantes como bodyParser, que nos permite manejar peticiones y enviar respuestas en formato JSON y errorHandler, que nos permitirá manejar errores en nuestro servidor.
./app/app.js
errorhandle
const express = require('express'); const bodyParser = require('body-parser'); const errorHandler = require('errorhandler'); // importar el middleware errorHandlerconst app = express(); const Product = require('./routes/prodRoutes'); //Nos permite manejar peticiones y enviar respuesta en formato json app.use(bodyParser.json()); //De esta manera indicamos que no vamos a recibir peticiones enviadas directamente de un formulario, sino que sera todo enviado en json app.use(bodyParser.urlencoded({extended: false})); app.use(errorHandler()); // Agregar el middleware errorHandler app.use('/prodRoutes', Product); module.exports = app;
./app/config/config.js
localhost
module.exports = { PORT: process.env.PORT || 3000, DB: process.env.DB || 'mongodb://localhost:27017/db' }
./app/config/db.js
useCreateIndex: true
, así que lo eliminé e igualmente funciona la API.const mongoose = require('mongoose'); mongoose.set('strictQuery', false); const CONFIG = require('./config'); module.exports = { connection: null, connect: function() { if (this.connection) returnthis.connection; return mongoose.connect(CONFIG.DB, { useUnifiedTopology: true, useNewUrlParser: true, // useCreateIndex: true }).then(connection => { this.connection = connection; console.log('Conexion a DB exitosa'); return connection; }).catch(err => { console.log(err); throw err; }) } }
./app/controller/prodController.js
const Product = require('../models/prodModels'); functionlistall(req, res) { Product.find({}) .then(prodModels => { if(prodModels.length) return res.status(200).send({prodModels}) return res.status(204).send({message: 'NO CONTENT'}); }).catch(err => res.status(500).send({err})) } functioncreate(req, res) { let product = new Product(req.body); product.save() .then(product => res.status(201).send({product}) ).catch(err => res.status(500).send({err})) } functionshow(req, res) { if (req.body.err) return res.status(500).send({err: req.body.err}); if (!req.body.prodModels) return res.status(404).send({message: 'Not Found'}); let prodModels = req.body.prodModels; return res.status(200).send({prodModels}); } functionupdate(req, res) { if(req.body.err) return res.status(500).send({err: req.body.err}); if(!req.body.prodModels) return res.status(404).send({message: 'Not Found'}); let product = req.body.prodModels[0]; product = Object.assign(product, req.body); product.save() .then(product => res.status(200).send({message: 'Product Updated', product}) ).catch(err => res.status(500).send({err})) } functiondeleted(req, res) { if(req.body.err) return res.status(500).send({err: req.body.err}); if(!req.body.prodModels) return res.status(404).send({message: 'Not Found'}); req.body.prodModels[0].remove() .then(product => { res.status(200).send({message:'Product removed', product}) } ).catch(err => res.status(500).send({err})); } functionfind(req, res, next){ let query = {}; query[req.params.key] = req.params.value Product.find(query).then(prodModels => { if(!prodModels.length) return next(); req.body.prodModels = prodModels; return next(); }).catch(error =>{ // corregido a error req.body.err = err; // corregido a error next(); }) } module.exports = { listall, show, create, update, deleted, find, }
./app/models/prodModels.js
+
const mongoose = require('mongoose'); // se agrega la importación de mongooseconst ProductSchema = new mongoose.Schema({ name: { type: String, unique: true, required: true }, price: { type: Number, required: true }, category: { type: String, required: true, enum:['Hogar', 'Cocina', 'Higiene'] }, stock: { type: Number, default: 10 }, date: { type: Date, default: Date.now() } }); module.exports = mongoose.model('Product', ProductSchema);
./app/routes/prodRoutes.js
const express = require('express'); const prodController = require('../controller/prodController'); const router = express.Router(); router.get('/', prodController.listall); // aquí se cambió el nombre del método router.post('/', prodController.create); router.get('/:name', prodController.show); router.put('/:name', prodController.update); router.delete('/:name', prodController.deleted); // aquí se cambió el nombre del métodomodule.exports = router;
Y la respuesta de la consola es:
¿qué otros cambios se hicieron en la aplicación?