Node.JS

Node.js y PostgreSQL – Ejemplo de conexión

Node JS puede ser conectado a PostgreSQL fácilmente a través del paquete pg.

En este tutorial vamos a ver cómo conectar Node.JS con PostgreSQL y hacer las cuatro operaciones básicas de la base de datos: Create, read, update y delete (CRUD); todo esto evitando inyecciones SQL y usando un poco el model MVC.

Para la interfaz vamos a usar Bootstrap (ya que será responsivo), y para que el usuario use nuestra app web vamos a usar Express.

Archivos que conforman el CRUD de PostgreSQL con Express

Aunque estos ejemplos son con la web, los mismos pueden ser ejecutados en la terminal sin ningún problema.

Requisitos y recomendaciones

Recuerda instalar Node.JS y también PostgreSQL.

Si no sabes mucho sobre Express, te recomiendo leer primero este post o este otro que usa express-generator.

¿Te gusta más MySQL? mira este CRUD con MySQL y Node.js

Instalar librería de pg

En el código que dejaré al final estará el package,json en donde están todas las dependencias de la app, sin embargo nunca sobra dejar el comando de instalación:

npm install --save pg

Conexión y base de datos

Vamos a conectarnos a una tabla de productos que tienen id autoincrementable, nombre y precio.

La estructura de la tabla es la siguiente:

create table productos(
    id serial primary key,
    nombre varchar(255),
    precio decimal(5, 2)
);

Nos vamos a conectar a través de ella creando una conexión a través de un pool. No olvides cambiar tus credenciales de acuerdo a tu configuración.

Si necesitas crear una base de datos con usuarios mira este post.

const { Pool } = require("pg")
// Coloca aquí tus credenciales
const pool = new Pool({
  user: "postgres",
  host: "127.0.0.1",
  database: "tienda",
  password: "hunter2",
  port: 5432,
});
module.exports = pool;

Las consultas

Cuando establecemos una conexión con la base de datos podemos llamar al método query con la consulta como cadena y opcionalmente un arreglo de valores que van a remplazar a los placeholders.

El placeholder es aquel indicado con $1, $2, etcétera y evita las inyecciones SQL. En los ejemplos veremos su uso.

Por cierto, vamos a usar async y await, una manera sencilla de abstraer las promesas.

El modelo

Basta de charlas, es hora de ver el modelo de productos que se encargará de interactuar con la base de datos:

const conexion = require("../conexion")
module.exports = {
    async insertar(nombre, precio) {
        let resultados = await conexion.query(`insert into productos
        (nombre, precio)
        values
        ($1, $2)`, [nombre, precio]);
        return resultados;
    },
    async obtener() {
        const resultados = await conexion.query("select id, nombre, precio from productos");
        return resultados.rows;
    },
    async obtenerPorId(id) {
        const resultados = await conexion.query(`select id, nombre, precio from productos where id = $1`, [id]);
        return resultados.rows[0];
    },
    async actualizar(id, nombre, precio) {
        const resultados = conexion.query(`update productos
        set nombre = $1,
        precio = $2
        where id = $3`, [nombre, precio, id]);
        return resultados;
    },
    async eliminar(id) {
        const resultados = conexion.query(`delete from productos
        where id = $1`, [id]);
        return resultados;
    },
}

Cuando hacemos una consulta que devuelve datos, los mismos vienen en la propiedad rows.

Si solo vamos a obtener un dato, accedemos a rows[0].

El controlador

Voy a usar el enrutador como controlador, pues se encargará de renderizar las vistas y pasarle datos a través de los modelos.

Si no sabes mucho sobre las rutas mira este post.

const express = require('express');
const router = express.Router();

const productosModel = require("../models/productos");

router.get('/', function (req, res, next) {
    productosModel
        .obtener()
        .then(productos => {
            console.log(productos);
            res.render("productos/ver", {
                productos: productos,
            });
        })
        .catch(err => {
            console.log(err);
            return res.status(500).send("Error obteniendo productos");
        });

});
router.get('/agregar', function (req, res, next) {
    res.render("productos/agregar");
});
router.post('/insertar', function (req, res, next) {
    // Obtener el nombre y precio. Es lo mismo que
    // const nombre = req.body.nombre;
    // const precio = req.body.precio;
    const { nombre, precio } = req.body;
    if (!nombre || !precio) {
        return res.status(500).send("No hay nombre o precio");
    }
    // Si todo va bien, seguimos
    productosModel
        .insertar(nombre, precio)
        .then(idProductoInsertado => {
            res.redirect("/productos");
        })
        .catch(err => {
            return res.status(500).send("Error insertando producto");
        });
});
router.get('/eliminar/:id', function (req, res, next) {
    productosModel
        .eliminar(req.params.id)
        .then(() => {
            res.redirect("/productos");
        })
        .catch(err => {
            return res.status(500).send("Error eliminando");
        });
});
router.get('/editar/:id', function (req, res, next) {
    productosModel
        .obtenerPorId(req.params.id)
        .then(producto => {
            if (producto) {
                res.render("productos/editar", {
                    producto: producto,
                });
            } else {
                return res.status(500).send("No existe producto con ese id");
            }
        })
        .catch(err => {
            return res.status(500).send("Error obteniendo producto");
        });
});
router.post('/actualizar/', function (req, res, next) {
    // Obtener el nombre y precio. Es lo mismo que
    // const nombre = req.body.nombre;
    // const precio = req.body.precio;
    const { id, nombre, precio } = req.body;
    if (!nombre || !precio || !id) {
        return res.status(500).send("No hay suficientes datos");
    }
    // Si todo va bien, seguimos
    productosModel
        .actualizar(id, nombre, precio)
        .then(() => {
            res.redirect("/productos");
        })
        .catch(err => {
            return res.status(500).send("Error actualizando producto");
        });
});

module.exports = router;

Como ves, algunas rutas se encargan de renderizar vistas y otras de recuperar datos del formulario. Para pasar datos a las vistas se pasa un objeto como segundo argumento a res.render.

Las vistas

Las vistas son creadas con EJS, aunque pueden ser creadas con cualquier otro motor de plantillas. Comencemos viendo el encabezado y el pie, pues serán unas cosas que siempre vamos a incluir en todas las demás plantillas.

Recuerda que está basado en una plantilla de Bootstrap 4.

<!doctype html>
<html lang="es">
<!--
  Plantilla inicial de Bootstrap 4
  @author parzibyte
  Visita: parzibyte.me/blog
-->

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="Conexión PostgreSQL y Node usando Express y Bootstrap">
    <meta name="author" content="Parzibyte">
    <title>Conexión PostgreSQL y Node usando Express y Bootstrap</title>
    <!-- Cargar el CSS de Boostrap-->
    <link href="/stylesheets/bootstrap.min.css" rel="stylesheet">
    <!-- Cargar estilos propios -->
    <link href="/stylesheets/style.css" rel="stylesheet">
</head>

<body>
    <!-- Definición del menú -->
    <nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
        <a class="navbar-brand" target="_blank" href="//parzibyte.me/blog">Node y PGSQL - By Parzibyte</a>
        <button aria-label="Mostrar u ocultar menú" class="navbar-toggler" id="botonMenu" type="button">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="menu">
            <ul class="navbar-nav mr-auto">
                <li class="nav-item">
                    <a class="nav-link" href="/productos">Ver</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="/productos/agregar">Agregar</a>
                </li>
            </ul>
        </div>
    </nav>
    <script type="text/javascript">
        // Tomado de https://github.com/parzibyte/cotizaciones_web/blob/master/js/cotizaciones.js#L2
        document.addEventListener("DOMContentLoaded", () => {
            const menu = document.querySelector("#menu"),
                botonMenu = document.querySelector("#botonMenu");
            if (menu) {
                botonMenu.addEventListener("click", () => menu.classList.toggle("show"));
            }
        });
    </script>
    <!-- Termina la definición del menú -->
    <main role="main" class="container">
        <div class="row">
</div>
</main>
</body>

</html>

Formulario para insertar datos

Insertar producto en base de datos de PostgreSQL

El formulario para insertar datos es el siguiente, fíjate en el atributo name que tiene cada input, pues a él accedemos desde el router de express.

<%- include("../header"); %>
<div class="col-12">
    <h1>Agregar producto</h1>
    <form method="post" action="/productos/insertar">
        <div class="form-group">
            <label for="nombre">Nombre</label>
            <input required id="nombre" placeholder="Nombre del producto" class="form-control" type="text"
                name="nombre">
        </div>
        <div class="form-group">
            <label for="precio">Precio</label>
            <input required id="precio" placeholder="Precio del producto" class="form-control" type="number"
                name="precio">
        </div>
        <div class="form-group">
            <button class="btn btn-success">Guardar</button>
            &nbsp;<a href="/productos" class="btn btn-primary">Volver</a>
        </div>
    </form>
</div>
<%- include("../footer"); %>

Editar producto

Para editar, debemos obtener el id producto de la URL, obtenerlo desde nuestro modelo y después rellenar el formulario.

Formulario de edición de producto para ser enviado a Express
<%- include("../header"); %>
<div class="col-12">
    <h1>Editar producto</h1>
    <form method="post" action="/productos/actualizar">

        <input value="<%= producto.id %>" name="id" type="hidden">
        <div class="form-group">
            <label for="nombre">Nombre</label>
            <input value="<%= producto.nombre %>" required id="nombre" placeholder="Nombre del producto"
                class="form-control" type="text" name="nombre">
        </div>
        <div class="form-group">
            <label for="precio">Precio</label>
            <input value="<%= producto.precio %>" required id="precio" placeholder="Precio del producto"
                class="form-control" type="number" name="precio">
        </div>
        <div class="form-group">
            <button class="btn btn-success">Guardar</button>
            &nbsp;<a href="/productos" class="btn btn-primary">Volver</a>
        </div>
    </form>
</div>
<%- include("../footer"); %>

Mostrar productos

Para mostrarlos dibujamos una tabla con los detalles del producto y además dos enlaces.

Un enlace es para eliminar y otro para editar.

<%- include("../header"); %>
<div class="col-12">
    <h1>Productos</h1>
    <a href="/productos/agregar" class="btn btn-primary mb-2">Agregar</a>
    <table class="table table-bordered">
        <thead>
            <tr>
                <th>Nombre</th>
                <th>Precio</th>
                <th>Editar</th>
                <th>Eliminar</th>
            </tr>
        </thead>
        <tbody>
            <% productos.forEach(producto => { %>
            <tr>
                <td><%= producto.nombre %></td>
                <td><%= producto.precio %></td>
                <td>
                    <a href="/productos/editar/<%= producto.id %>" class="btn btn-warning">
                        Editar
                    </a>
                </td>
                <td>
                    <a href="/productos/eliminar/<%= producto.id %>" class="btn btn-danger">
                        Eliminar
                    </a>
                </td>
            </tr>
            <% })%>
        </tbody>
    </table>
</div>
<%- include("../footer"); %>

Los enlaces parecen botones gracias a la clase btn, y a sus variantes.

Poniendo todo junto

CRUD PostgreSQL y Node

Si quieres descarga el código de GitHub, asegúrate de haber instalado Node y PGSQL.

Dirígete a la carpeta del proyecto e instala las dependencias con:

npm install

Cuando se acaben de instalar ejecuta el proyecto con:

set debug=crud-postgresql-node:* & npm start

Finalmente visita localhost:3000

Conclusión

Node.JS y sus paquetes ayudan a que el desarrollador se centre en la lógica de la aplicación, pues solo tiene que conectar los paquetes entre sí y programar la lógica de negocio.

Dejo un enlace a la documentación oficial del paquete.

Te invito a leer más sobre Node y PostgreSQL.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.
parzibyte

Programador freelancer listo para trabajar contigo. Aplicaciones web, móviles y de escritorio. PHP, Java, Go, Python, JavaScript, Kotlin y más :) https://parzibyte.me/blog/software-creado-por-parzibyte/

Ver comentarios

Entradas recientes

Creador de credenciales web – Aplicación gratuita

Hoy te voy a presentar un creador de credenciales que acabo de programar y que…

2 horas hace

Desplegar PWA creada con Vue 3, Vite y SQLite3 en Apache

Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…

6 días hace

Arquitectura para wasm con Go, Vue 3, Pinia y Vite

En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…

6 días hace

Vue 3 y Vite: crear PWA (Progressive Web App)

En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…

6 días hace

Errores de Comlink y algunas soluciones

Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…

7 días hace

Esperar promesa para inicializar Store de Pinia con Vue 3

En este artículo te voy a enseñar cómo usar un "top level await" esperando a…

7 días hace

Esta web usa cookies.