Un CRUD no es más que una aplicación, que de una u otra manera, nos permite operar sobre un recurso de todas las maneras que HTTP contempla. Es decir, podemos visualizar los recursos grupal o individualmente, editar o eliminar estos así como crear recursos nuevos a nuestra aplicación.

Para ello, antes de trabajar en la aplicación necesitamos crear una API. Esta mendiante rutas que serán expuestas se encargará de obtener los datos, tanto que nosotros le enviamos como que la base de datos le responderá y de responder ante nuestras peticiones tal y como programemos.

¿Con qué librerías vamos a trabajar?

Para este tutorial, deberemos de usar:

  • MongoDB como base de datos.

  • Mongoose para trabajar con la base de datos.

  • Express como servidor de nuestra API. Este gustionará las rutas.

Paso 1: Instalar MongoDB en nuestro equipo

Para ello, visitaremos la sección Download del sitio de MongoDB y en este descargaremos la versión para la comunidad.

El proceso de instalación es muy sencillo, si te surge alguna duda recuerda que la sección de comentarios está para poder ayudarte.

Paso 2: Iniciar nuestro proyecto: Hello World en Express.

Antes que nada, instalaremos de una vez la totalidad de las librerías que necesitamos:

$ yarn init
$ yarn add express body-parser mongoose

La librería body-parser se encargará de obtener los datos que enviemos en peticiones como las de tipo POST, en las que siempre enviamos un cuerpo con la petición. Si no lo usamos, al solicitar este en nuestro código obtendremos {} sin importar lo que contenga.

Una vez lo tengamos todo listo, en el archivo index.js crearemos un Hello World básico a fin de ver si Express funciona correctamente:

const express = require('express')

const app = express()
const port = process.env.PORT || '3000'

app.get('/', (req, res) => {
    res.send('Hello World')
})

app.listen(port, () => {
    console.log(`[Express App] The app is listening on port: ${port}`)
})

Deberá de aparecer el mensaje de Hello World si vamos a http://localhost:3000

¡Recuerda! Para que el mensaje aparezca tienes que inciar el servidor. Eso se hace con el comando node index.js

Paso 2: Crear el modelo en la base de datos

Para poder trabajar con un recurso, deberemos de identificarlo en la base da datos. Esta identificación pasa por la creación de un modelo.

En nuestro caso, el modelo que deberemos de crear es sobre un curso. En mi caso lo llamé Post.

Creamos el archivo en models/post.js:

const mongoose = require('mongoose')

const PostSchema = new mongoose.Schema({
    title: String,
    contents: Array,
    image: String,
    releaseDate: Date,
    special: boolean
})

module.exports = mongoose.model('Post', PostSchema)

Como ves, basándonos en la propiedad de Schema, creamos uno nuevo con las características que necesitamos y lo identificamos. Tal y como cuando trabajamos con un lenguaje tipado. Después usando mongoose.model() lo registramos con un nombre.

Este archivo es muy importante ya que deberemos de importarlo cuando querramos hacer operaciones con la base de datos(leer recursos, escribir uno nuevo…).

Paso 3: Escribiendo las funciones para trabajar con los datos

Como te dije, ahora necesitaremos el modelo para poder trabajar con datos. Crearemos un archivo en /lib/post.js. Este será una colección de funciones que luego usaremos en las rutas, y que simplemente usarán las funciones del modelo para su cometido y devolverán el resultado.

const Post = require('../models/post')

module.exports = {
    async getPosts() {
        const Posts = await Post.find({}).sort({ "releaseDate": -1 })
        // El sort se encaraga de ordenar los posts de más reciente a menos usando releaseDate, que es la fecha de lanzamiento de estos.
        return Posts
    },
    async getPost(id) {
        const currentPost = await Post.findById(id)
        return currentPost
    },
    async createOrUpdatePost(post) {
        if(post._id) {
            const updatedPost = await Post.findByIdAndUpdate(post._id, post, { new: true })
            return updatedPost
        }

        const newPost = await Post.create(post)
        return newPost
    },
    async deletePost(id) {
        const deletedPost = await Post.findByIdAndRemove(id)
        return deletedPost
    }
}

Con el uso de async/await nos aseguramos que nuestros datos están listos, y en caso contrario la función esperará a estos. Nos permite escribir el código de una manera muy limpia y como ves la mayororía de funciones no son más que dos líneas.

Con respecto a la función createOrUpdatePost(post) en el caso de que le mandemos un objeto con un id este será usado para actualizar el elemento con dicho id. En caso contrario creará uno nuevo.

Paso 4: Preparando las funciones de las rutas.

En un archivo separado(routes/api.js) crearemos todas las funciones que le pasaremos a Express en nuestro archivo principal. Para ello las funciones deberán de tener los parámetros req y res propios de las funciones de rutas de Express.JS.

Mi archivo quedó tal que así:

const Db = require('../lib/post')

module.exports = {
    loadPosts: async (req, res) => {
        const Entries = await Db.getPosts()
        res.status(200) // 200 => Todo está O.K.
        res.json(Entries)
    },
    loadPost: async (req, res) => {
        const Entry = await Db.getPost(req.params.id)
        res.status(200)
        res.json(Entry)
    },
    newPost: async (req, res) => {
        const newEntry = await Db.createOrUpdatePost(req.body)
        res.status(201) // 201 => Hay nuevo contenido.
        res.json(newEntry)
    },
    updatePost: async (req, res) => {
        const updatedEntry = await Db.createOrUpdatePost(req.body)
        res.status(201)
        res.json(updatedEntry)
    },
    deletePost: async (req, res) => {
        const deletedEntry = await Db.deletePost(req.params.id)
        res.status(204) // 204 => No existe contenido.
        res.json(deletedEntry)
    }
}

Paso 5: Asignando las funciones a rutas en nuestro archivo principal

Ya hemos creado las funciones de nuestras rutas, aunque, hasta que no se las asignemos a una ruta no nos servirán de nada. Para ello volvemos al archivo index.js y en este(yo lo coloqué debajo de la ruta /) asignamos las rutas de la siguiente manera:


const apiRoutes = require('./routes/api') // Añádelo debajo de el require de Express.

app.get('/api/posts/', apiRoutes.loadPosts)
app.get('/api/posts/:id', apiRoutes.loadPost)
app.post('/api/posts/', apiRoutes.newPost)
app.put('/api/posts/', apiRoutes.updatePost) // No lleva parámetro id, ya que lo mandamos en el body.
app.delete('/api/posts/:id', apiRoutes.deletePost)

Paso 6: Conectando la base de datos

¡Listo! Nuestras rutas ya deben de funcionar sin problemas. El problema es que no conectamos ninguna base de datos por lo que no podremos registrar el modelo ni operar sobre este, para ello en el mismo archivo index.js añadimos lo siguiente:

const mongoose = require('mongoose')

const mongoUri = process.env.MONGO_URI || 'mongodb://localhost:27017/openwebinars'


mongoose.connect(mongoUri)

Como ves, la varible que guarda nuestra URI puede ser sustituida por una variable de entorno. Esto nos servirá al hacer deploy a un servidor de producción. Ya que no usaremos nuestra base de datos local en mucho de los casos(podemos usar un servicio externo, tenerla en otro servidor…).

Paso 7: Añadiendo body-parser a Express

Como te comenté al principio del artículo, para poder leer el cuerpo de una petición tenemos que usar el middleware body-parser y añadirlo a express.

Un middleware es una pieza de código(generalmente una función) que se ejecuta entre dos procesos. Por ejemplo, body-parser se ejecutará entre la solicitud de la ruta y la función que tengamos como respuesta.

Lo haremos de la siguiente manera(en nuestro archivo index.js):

const bodyParser = require('body-parser')

app.use(bodyParser.json()) // Convertirá el cuerpo en un objeto JSON.

Paso 8: Añadiendo funciones de Error Handling

Antes de finalizar, añadiremos al final del archivo en el que venimos trabajando en estos pasos una pequeña función que mandará por consola el error que se le pase, tal que así:

function handleError(err) {
    console.error(`[Error] ${err.message}`)
    console.error(err.stack)
}

Y se la asignaremos a los eventos error, uncaughtException y unHandledRejection(este último relacionado con Promesas) de la siguiente manera:

app.on('error', (err) => handleError)
app.on('uncaughtException', (err) => handleError)
app.on('unhandledRejection', (err) => handleError)

Así podremos controlar algunos errores en nuestro código en el caso de que se produzcan, y cuando tengamos la aplicación en un servidor que estos hagan parte de los logs, para poder intentar solventar futuras complicaciones.

Paso 9: Deploy a Now.sh

Una vez tenemos nuestra aplicación funcionando sin ningún tipo de inconvenientes haremos deploy a el servicio de Now, que hemos usado en más artículos.

Debemos tener algunas consideraciones previas en cuenta:

  • Debemos añadir un script de start en el package.json antes de hacer deploy:
"scripts": {
    "start": "node index.js"
  },
  • Now no trabaja con bases de datos, por lo que tendremos que usar un servicio externo. Para ello usaremos MLab.

Antes de hacer deploy, registraremos en una variable de MongoDB nuestra URI de MLab, si tienes problemas para conseguirla no dudes en consultar en los comentarios.

$ now secrets add OPENWEBINARS_URI "turidemlab"

Finalmente, haremos deploy teniendo en cuenta que tenemos que asignar nuestro nuevo secret como variable de entorno a la instancia de Now:

$ now -e MONGO_URI=@openwebinars_uri

Conclusiones

Podemos crear una API y hacerla funcionar de manera muy fácil, ligera en cuanto a librerías y usando pocas líneas de código. En la segunda parte, comenzaremos a trabajar con esta API para crear el FrontEnd de nuestra aplicación.

Enlaces relacionados

MongoDB - Página Principal
ExpressJS - Página Prinicpal
MongooseJS - Página Prinicipal
MongooseJS - Docs
Repositorio de la API en Github
OpenWebinars CRUD API - Versión LIVE en Now.sh