En este tutorial, demostrar茅 c贸mo usar SQLite en combinaci贸n con JavaScript dentro del entorno Node.js con la ayuda del controlador sqlite3 Node.js. Para aquellos que no est谩n familiarizados con SQLite, es una base de datos relacional simple de un solo archivo que es muy popular entre los dispositivos inteligentes, los sistemas integrados e incluso las aplicaciones web peque帽as.
Configuraci贸n e instalaci贸n
Contenido
Comenzar茅 creando un nuevo paquete npm usando npm init
dentro de un directorio vac铆o llamado node-sqlite-tutorial.
$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sane defaults.
See `npm help json` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg> --save` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
name: (app) node-sqlite
version: (0.0.0) 0.1.0
description: Code for tutorial blog on node and sqlite
entry point: (index.js) main.js
test command:
git repository:
keywords:
author: Adam McQuistan
license: (BSD) MIT
About to write to /node-sqlite/app/package.json:
{
"name": "node-sqlite",
"version": "0.1.0",
"description": "Code for tutorial blog on node and sqlite",
"main": "main.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"repository": "",
"author": "Adam McQuistan",
"license": "MIT"
}
Is this ok? (yes)
A continuaci贸n, tendr茅 que instalar sqlite3 paquete a trav茅s de npm as铆:
$ npm install --save sqlite3
Adem谩s de sqlite3 voy a instalar Azulejo para poder usar la funcionalidad de promesa familiar en la programaci贸n de mi base de datos.
$ npm install --save bluebird
Ahora crear茅 un archivo vac铆o junto al archivo package.json llamado database.sqlite3 en el que SQLite almacenar谩 los datos.
Dise帽o de la base de datos
Como con casi todos mis otros art铆culos, usar茅 una aplicaci贸n inventada para ayudar a describir algunos de los aspectos importantes de la programaci贸n de bases de datos con Node.js y SQLite. Para este art铆culo, asumo que estoy construyendo la capa de acceso a datos para una aplicaci贸n de seguimiento de proyectos y tareas. Las reglas comerciales b谩sicas para la capa de acceso a datos de esta aplicaci贸n son las siguientes:
- La aplicaci贸n tiene proyectos
- Cada proyecto puede tener una o m谩s tareas para completar
Con las reglas comerciales establecidas, puedo tomar esa informaci贸n y comenzar a dise帽ar las tablas necesarias y sus campos. Est谩 claro que necesitar茅 un proyectos mesa, as铆 como una Tareas mesa. Por lo dem谩s, usar茅 un poco de intuici贸n, algunos datos de prueba inventados y seguir茅 con ellos (una caracter铆stica de trabajo com煤n para la mayor铆a de los desarrolladores).
tabla de proyectos
nombre de identificaci贸n
1 | Escribir Node.js – Tutorial de SQLite |
tabla de tareas
id nombre descripci贸n isCompleted projectId
1 | contorno | Resumen de alto nivel de las secciones | 1 | 1 |
2 | Escribir | Escribir contenido de art铆culos y ejemplos de c贸digo | 0 | 1 |
Bien, ahora que s茅 lo que necesito crear, ahora puedo traducirlo en c贸digo.
Creando la base de datos
Para comenzar, necesitar茅 hacer un archivo main.js junto con un dao.js (o Objeto de acceso a datos) en el mismo directorio que el archivo package.json.
Dentro de dao.js agregar茅 importaciones para sqlite3 y Bluebird’s Promise
objetos. Despu茅s de eso, distribuir茅 una clase de acceso a datos llamada AppDAO
que establecer谩 una conexi贸n a la base de datos dentro de un constructor y la asignar谩 a un campo miembro llamado db
.
// dao.js
const sqlite3 = require('sqlite3')
const Promise = require('bluebird')
class AppDAO {
constructor(dbFilePath) {
this.db = new sqlite3.Database(dbFilePath, (err) => {
if (err) {
console.log('Could not connect to database', err)
} else {
console.log('Connected to database')
}
})
}
}
module.exports = AppDAO
La conexi贸n es bastante sencilla. Simplemente crea una instancia de sqlite3 Database
constructor de clases pas谩ndole la ruta al archivo de base de datos SQLite con el que desea conectarse y, opcionalmente, verifique los errores que puedan ocurrir. Como se se帽al贸 anteriormente, estoy almacenando este objeto de conexi贸n en un campo llamado db
sobre el AppDAO
clase.
Avanzar茅 explicando c贸mo usar el objeto de conexi贸n para enviar consultas a la base de datos. El paquete sqlite3 Node.js ofrece varios m茅todos diferentes para ejecutar consultas, pero en los que me centrar茅 en este tutorial son:
run
: se utiliza para crear o modificar tablas y para insertar o actualizar datos de tablasget
: seleccione una sola fila de datos de una o m谩s tablasall
: seleccione varias filas de datos de una o m谩s tablas
Para empezar, me gustar铆a explorar el run
m茅todo. Su sintaxis general se ve as铆:
db.run('SOME SQL QUERY', [param1, param2], (err) => {
if (err) {
console.log('ERROR!', err)
}
})
El primer par谩metro pasado a run(...)
es una cadena de SQL que se ejecutar谩 y es el 煤nico par谩metro obligatorio. El segundo es una matriz opcional de par谩metros que la biblioteca sqlite3 intercambiar谩 por cualquier ‘?’ marcadores de posici贸n dentro de la consulta (lo demostrar茅 en un momento). La final es una funci贸n de devoluci贸n de llamada de error.
Como puede sospechar, usar茅 el run(...)
funci贸n para crear y actualizar mis proyectos y tareas. Sin embargo, en realidad voy a envolverlo en mi propia versi贸n de un run
m茅todo en el AppDAO
clase porque me gustar铆a encapsularlo en una bluebird
Promise
para hacer las cosas expl铆citamente asincr贸nicas y basadas en promesas as铆:
// dao.js
const sqlite3 = require('sqlite3')
const Promise = require('bluebird')
class AppDAO {
// omitting constructor code
run(sql, params = []) {
return new Promise((resolve, reject) => {
this.db.run(sql, params, function (err) {
if (err) {
console.log('Error running sql ' + sql)
console.log(err)
reject(err)
} else {
resolve({ id: this.lastID })
}
})
})
}
}
Con mi costumbre AppDAO.run(...)
m茅todo que ahora puedo utilizar para crear las tablas de productos y tareas.
Para comenzar, agrego dos archivos m谩s a mi proyecto llamados project_repository.js y task_repository.js. Dentro de project_repository.js defino una clase llamada ProjectRepository
que tiene un constructor que acepta una instancia del AppDAO
objeto y un createTable
m茅todo que ejecuta algunos DDL (lenguaje de definici贸n de datos) SQL as铆:
// project_repository.js
class ProjectRepository {
constructor(dao) {
this.dao = dao
}
createTable() {
const sql = `
CREATE TABLE IF NOT EXISTS projects (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT)`
return this.dao.run(sql)
}
}
module.exports = ProjectRepository;
Luego hago esencialmente lo mismo de nuevo, pero esta vez en el archivo task_repository.js.
// task_repository.js
class TaskRepository {
constructor(dao) {
this.dao = dao
}
createTable() {
const sql = `
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
description TEXT,
isComplete INTEGER DEFAULT 0,
projectId INTEGER,
CONSTRAINT tasks_fk_projectId FOREIGN KEY (projectId)
REFERENCES projects(id) ON UPDATE CASCADE ON DELETE CASCADE)`
return this.dao.run(sql)
}
}
module.exports = TaskRepository;
El SQL DDL para crear las tablas est谩 completo, por lo que pasar茅 a los m茅todos para insertar datos en las tablas.
Insertar datos
En el ProjectRepository
clase necesito agregar un create
m茅todo que recibe el nombre del proyecto para crear y ejecuta la instrucci贸n INSERT apropiada usando el AppDAO.run(...)
m茅todo. Observe c贸mo he usado ‘?’ para representar el valor del nombre del proyecto y luego poner el name
par谩metro en el argumento de matriz de params opcional al run(...)
m茅todo. Esto se conoce como una declaraci贸n de consulta parametrizada que limpiar谩 las entradas para minimizar los riesgos de inyecci贸n de SQL.
// project_repository.js
class ProjectRepository {
// omitting other methods
create(name) {
return this.dao.run(
'INSERT INTO projects (name) VALUES (?)',
[name])
}
}
module.exports = ProjectRepository;
Se necesita un m茅todo de creaci贸n similar para TaskRepository
clase.
// task_repository.js
class TaskRepository {
// omitting other methods
create(name, description, isComplete, projectId) {
return this.dao.run(
`INSERT INTO tasks (name, description, isComplete, projectId)
VALUES (?, ?, ?, ?)`,
[name, description, isComplete, projectId])
}
}
module.exports = TaskRepository;
Ahora que tengo la capacidad de INSERTAR datos en la base de datos, me gustar铆a agregar la funcionalidad para actualizarla.
Actualizaci贸n de datos
En el ProjectRepository
clase voy a agregar un update
m茅todo que toma un project
objeto y actualiza todos los campos para el registro de la base de datos de ese proyecto nuevamente utilizando el AppDAO.run(...)
m茅todo, as铆:
// project_repository.js
class ProjectRepository {
// omitting other methods
update(project) {
const { id, name } = project
return this.dao.run(
`UPDATE projects SET name = ? WHERE id = ?`,
[name, id]
)
}
}
module.exports = ProjectRepository;
El siguiente paso es agregar el m茅todo de actualizaci贸n correspondiente a TaskRepository
clase.
// task_repository.js
class TaskRepository {
// omitting other methods
update(task) {
const { id, name, description, isComplete, projectId } = task
return this.dao.run(
`UPDATE tasks
SET name = ?,
description = ?,
isComplete = ?,
projectId = ?
WHERE id = ?`,
[name, description, isComplete, projectId, id]
)
}
}
module.exports = TaskRepository;
Eliminar datos
La 煤ltima funcionalidad mutacional a implementar es proporcionar la capacidad de eliminar registros de la base de datos. Para esto volver茅 a usar el AppDAO.run(...)
m茅todo junto con el nuevo delete
m茅todos tanto para el ProjectRepository
y TaskRepository
clases.
Xa ProjectRepository
esto se ve as铆:
// project_repository.js
class ProjectRepository {
// omitting other methods
delete(id) {
return this.dao.run(
`DELETE FROM projects WHERE id = ?`,
[id]
)
}
}
module.exports = ProjectRepository;
Y para TaskRepository
se parece a esto:
// task_repository.js
class TaskRepository {
// omitting other methods
delete(id) {
return this.dao.run(
`DELETE FROM tasks WHERE id = ?`,
[id]
)
}
}
module.exports = TaskRepository;
Muy bien, eso resume todas las formas en que usar茅 el run
m茅todo. A continuaci贸n presentar茅 los otros dos relacionados get
y all
M茅todos del paquete sqlite3 Node.js.
Leer datos
En esta secci贸n voy a repasar c贸mo usar el get
y all
m茅todos de la biblioteca sqlite3 Node.js. Como se mencion贸 previamente, get
se utiliza para recuperar una sola fila de datos mientras all
se utiliza para consultar muchas filas de datos.
La sintaxis b谩sica para usar get
Se ve como esto:
db.get('SELECT ...', [param1, param2], (err, result) => {
if (err) {
console.log(err)
} else {
// do something with result
}
})
D贸nde db
es un objeto de conexi贸n sqlite3. Notar谩s que la sintaxis es esencialmente id茅ntica a la run
excepto que la devoluci贸n de llamada tiene un par谩metro adicional que contiene el objeto de resultado de la consulta, asumiendo que no se arroj贸 ning煤n error.
La sintaxis base para all
es esencialmente el mismo de nuevo, excepto que el segundo par谩metro de la devoluci贸n de llamada es una matriz de resultados devueltos por la consulta, as铆:
db.all('SELECT ...', [param1, param2], (err, results) => {
if (err) {
console.log(err)
} else {
// do something with results
}
})
Al igual que hice con sqlite3 run
m茅todo voy a implementar el get
y all
m茅todos que utilizan el bluebird
Promise
dentro de AppDAO
clase como se muestra a continuaci贸n:
// dao.js
const sqlite3 = require('sqlite3').verbose()
const Promise = require('bluebird')
class AppDAO {
// omitting other methods
get(sql, params = []) {
return new Promise((resolve, reject) => {
this.db.get(sql, params, (err, result) => {
if (err) {
console.log('Error running sql: ' + sql)
console.log(err)
reject(err)
} else {
resolve(result)
}
})
})
}
all(sql, params = []) {
return new Promise((resolve, reject) => {
this.db.all(sql, params, (err, rows) => {
if (err) {
console.log('Error running sql: ' + sql)
console.log(err)
reject(err)
} else {
resolve(rows)
}
})
})
}
}
Ahora puedo usar estos m茅todos en el ProjectRepository
y TaskRepository
clases para recuperar datos de la base de datos SQLite.
Para empezar a帽adir茅 getById
m茅todos a cada clase para seleccionar sus registros por id.
En ProjectRepository
Agrego esto:
// project_repository.js
class ProjectRepository {
// omitting other methods
getById(id) {
return this.dao.get(
`SELECT * FROM projects WHERE id = ?`,
[id])
}
}
module.exports = ProjectRepository;
Y en TaskRepository
similar:
// task_repository.js
class TaskRepository {
// omitting other methods
getById(id) {
return this.dao.get(
`SELECT * FROM tasks WHERE id = ?`,
[id])
}
}
module.exports = TaskRepository;
Para demostrar el AppDAO.all(...)
m茅todo Agregar茅 la capacidad de seleccionar todos los proyectos, as铆 como todas las tareas para un proyecto determinado.
El c贸digo para SELECCIONAR todos los proyectos se ve as铆:
// project_repository.js
class ProjectRepository {
// omitting other methods
getAll() {
return this.dao.all(`SELECT * FROM projects`)
}
}
module.exports = ProjectRepository;
Luego, para seleccionar todas las tareas para un proyecto, usar茅 un m茅todo llamado getTasks(projectId)
que espera la identificaci贸n del proyecto para el que desea las tareas.
// project_repository.js
class ProjectRepository {
// omitting other methods
getTasks(projectId) {
return this.dao.all(
`SELECT * FROM tasks WHERE projectId = ?`,
[projectId])
}
}
module.exports = ProjectRepository;
Uso del c贸digo de acceso a datos
Hasta ahora, b谩sicamente he creado una biblioteca de acceso a datos para este proyecto ficticio y aplicaci贸n de seguimiento de tareas. Lo que me gustar铆a hacer ahora es usarlo para cargar mis datos de prueba que se muestran en las tablas en el Dise帽o de la base de datos secci贸n.
En el archivo main.js querr茅 introducir el AppDAO
, ProjectRepository
y TaskRepository
clases a trav茅s de require
. Luego los usar茅 para crear las tablas, completarlas con datos y luego recuperar datos de la base de datos y mostrarlos en la consola.
// main.js
const Promise = require('bluebird')
const AppDAO = require('./dao')
const ProjectRepository = require('./project_repository')
const TaskRepository = require('./task_repository')
function main() {
const dao = new AppDAO('./database.sqlite3')
const blogProjectData = { name: 'Write Node.js - SQLite Tutorial' }
const projectRepo = new ProjectRepository(dao)
const taskRepo = new TaskRepository(dao)
let projectId
projectRepo.createTable()
.then(() => taskRepo.createTable())
.then(() => projectRepo.create(blogProjectData.name))
.then((data) => {
projectId = data.id
const tasks = [
{
name: 'Outline',
description: 'High level overview of sections',
isComplete: 1,
projectId
},
{
name: 'Write',
description: 'Write article contents and code examples',
isComplete: 0,
projectId
}
]
return Promise.all(tasks.map((task) => {
const { name, description, isComplete, projectId } = task
return taskRepo.create(name, description, isComplete, projectId)
}))
})
.then(() => projectRepo.getById(projectId))
.then((project) => {
console.log(`nRetreived project from database`)
console.log(`project id = ${project.id}`)
console.log(`project name = ${project.name}`)
return projectRepo.getTasks(project.id)
})
.then((tasks) => {
console.log('nRetrieved project tasks from database')
return new Promise((resolve, reject) => {
tasks.forEach((task) => {
console.log(`task id = ${task.id}`)
console.log(`task name = ${task.name}`)
console.log(`task description = ${task.description}`)
console.log(`task isComplete = ${task.isComplete}`)
console.log(`task projectId = ${task.projectId}`)
})
})
resolve('success')
})
.catch((err) => {
console.log('Error: ')
console.log(JSON.stringify(err))
})
}
main()
Ejecutar usando node
Me gusta esto:
$ node main.js
Y ver谩 la salida como se muestra a continuaci贸n.
Connected to database
Retreived project from database
project id = 1
project name = 1
Retrieved project tasks from database
task id = 1
task name = Outline
task description = High level overview of sections
task isComplete = 1
task projectId = 1
task id = 2
task name = Write
task description = Write article contents and code examples
task isComplete = 0
task projectId = 1
Conclusi贸n
En este tutorial, he revisado los conceptos b谩sicos de la API del paquete sqlite3 de Node.js y he demostrado c贸mo puede envolver esa funcionalidad en JavaScript orientado a objetos con un enfoque en una implementaci贸n asincr贸nica basada en Promise.
Como siempre, les agradezco su lectura y agradezco los comentarios y cr铆ticas a continuaci贸n.