3

Creacion API con NodeJs, mongoose y express

<h1>Creacion de una API desde 0 usando NodeJs</h1>

The Basics

Introduccion

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:

  • Crear productos
  • Listar productos
  • Buscar productos por parametros
  • Actualizar los productos
  • Eliminar productos

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.

Crear Archivo base

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

Package.json: Scripts

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

Estructura

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:

  • app
    • config
      • config.js
      • database.js
    • controllers
      • ProductController.js
    • models
      • Product.js
    • routes
      • product.js
    • app.js

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:

Imgur

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.

<h3>models/Product.js</h3>

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

Resumen

  • Creamos un folder llamado api con el comando mkdir <folder_name> , desde la terminal
  • Ingresamos al folder creado con el comando cd <{path}/folder_name>.
  • Iniciamos el proyecto en nodeJs con el comando npm init -y
  • Instalamos las dependencias para trabajar
    • npm i express body-parser mongoose
    • npm i -D nodemon
  • Agregamos "dev": "nodemon server.js" en la seccion scripts de nuestro archivo package.json
  • Creamos el archivo server.js en nuestro folder usando en terminal touch server.js
  • Creamos la logica en carpetas para llegar al modelo MVC
  • Creamos los archivos js dentro del modelo MVC
  • Modificamos nuestro archivo principal app.js para ejecutaren modo desarrollo nuestra API con CRUD
Escribe tu comentario
+ 2
1
15361Puntos

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.


Primero cambié los nombres de algunos archivos para hacerlos más descriptivos y dev friendly.

./app/app.js

  • Para este archivo instalé 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

  • Aquí cambié la ruta del localhost
module.exports = {
    PORT: process.env.PORT || 3000,
    DB: process.env.DB || 'mongodb://localhost:27017/db'
}

./app/config/db.js

  • En este archivo tuve muchos problemas con el método 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

  • En este archivo se cambiaron algunas partes de algunas funciones…
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

  • Este archivo cambió drásticamente…
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:

user@d-caldasCaridad:~/API-REST$ npm run dev

> [email protected] dev
> nodemon server.js
> [email protected] dev
> nodemon server.js

[nodemon] 2.0.20
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node server.js`
Servidor corriendo en el puerto: 3000
Conexion a DB exitosa

¿qué otros cambios se hicieron en la aplicación?

Se han hecho varias correcciones. Entre ella:

  • En la definición de rutas del archivo ./app/routes/prodRoutes.js se han corregido los nombres de los métodos de delete y listAll para que se ajusten a los nombres de los métodos en el archivo ./app/controller/prodController.js.
  • En el archivo ./app/controller/prodController.js, se ha corregido el nombre de la variable en la función catch en el método find.
  • En el archivo ./app/config/db.js se ha corregido una letra en la propiedad useCreateIndex.
  • Se han importado los módulos mongoose en el archivo ./app/models/prodModels.js y errorhandler en el archivo ./app/app.js.
  • Se han agregado middleware de manejo de errores en el archivo ./app/app.js.
  • Se han agregado las importaciones necesarias para correr la aplicación.