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.
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
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>
<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.
<%- 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>
<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
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.
Gracias ya encontré el proyecto en git
Excelente información, puedes compartir el proyecto con el código
Lea el post