Un tutorial de SQLite con Node.js

    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

    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

    1Escribir Node.js – Tutorial de SQLite

    tabla de tareas

    id nombre descripci贸n isCompleted projectId

    1contornoResumen de alto nivel de las secciones11
    2EscribirEscribir contenido de art铆culos y ejemplos de c贸digo01

    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 tablas
    • get: seleccione una sola fila de datos de una o m谩s tablas
    • all: 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, ProjectRepositoryy 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.

     

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *