An Async Node.js Sqlite Helper

When I first built my media repository API in Node.js, I ran into callback hell when running queries or commands against the database.

As an example:

static readonly GetAllVideoGameSystems = (callback: (error: string | null, systems: VideoGameSystem[]) => void) => {
  const db = this.GetDatabase();

  const systems: VideoGameSystem[] = [];

  db.all(getAllVideoGameSystems, (err: any, rows: VideoGameSystemQueryReturn[]) => {
    db.close();

    if (err) {
      return callback(cleanSqliteError(err), []);
    }

    rows.forEach((row) => {
      systems.push({
        videoGameSystemId: row.VideoGameSystemId,
        name: row.Name,
        colorCode: row.ColorCode,
        videoGameCount: row.VideoGameCount,
      });
    });

    return callback(null, systems);
  });
};

I recently started work on another tool to manage my fitness data and I decided that this would not be sustainable. So after doing some searching, and thanks to some help from devs on Stack Overflow, I ended up building a new class to do my sqlite database calls. I figured I'd share the code in hopes that maybe it will help you. I loosely modeled it off of Dapper in .NET.

This does require the sqlite3 npm package.

db.ts
import sqlite3 from 'sqlite3';

const cleanSqliteError = (error: Error): string => error.message.replace('SQLITE_ERROR: ', '');

class db {
  private static GetDatabase = () => new sqlite3.Database('./path/to/db.db');

  static async Query<T>(sql: string, params: any = []): Promise<[error: string | null, data: T[]]> {
    return new Promise(function (resolve, reject) {
      try {
        const database = db.GetDatabase();

        database.all(sql, params, (err: Error, rows: T[]) => {
          database.close();

          if (err) {
            return reject([cleanSqliteError(err), []]);
          }

          resolve([null, rows]);
        });
      } catch (e) {
        return reject(e);
      }
    });
  }

  static async QuerySingle<T>(sql: string, params: any = []): Promise<[error: string | null, data: T | null]> {
    return new Promise(function (resolve, reject) {
      try {
        const database = db.GetDatabase();

        database.get(sql, params, (err: Error, row: T) => {
          database.close();

          if (err) {
            return reject([cleanSqliteError(err), []]);
          }

          if (!row) {
            return resolve([null, null]);
          }

          resolve([null, row]);
        });
      } catch (e) {
        return reject(e);
      }
    });
  }

  static async Execute(sql: string, params: any = []): Promise<string | null> {
    return new Promise(function (resolve, reject) {
      try {
        const database = db.GetDatabase();

        database.run(sql, params, (err: Error) => {
          database.close();

          if (err) {
            return reject(cleanSqliteError(err));
          }

          resolve(null);
        });
      } catch (e) {
        return reject(e);
      }
    });
  }
}

export { db };

Or, alternatively, if you prefer a JS version,

db.js
import sqlite3 from 'sqlite3';

const cleanSqliteError = (error) => error.message.replace('SQLITE_ERROR: ', '');

class db {
  private static GetDatabase = () => new sqlite3.Database('./path/to/db.db');

  static async Query(sql, params = []) {
    return new Promise(function (resolve, reject) {
      try {
        const database = db.GetDatabase();

        database.all(sql, params, (err, rows) => {
          database.close();

          if (err) {
            return reject([cleanSqliteError(err), []]);
          }

          resolve([null, rows]);
        });
      } catch (e) {
        return reject(e);
      }
    });
  }

  static async QuerySingle(sql, params = []) {
    return new Promise(function (resolve, reject) {
      try {
        const database = db.GetDatabase();

        database.get(sql, params, (err, row) => {
          database.close();

          if (err) {
            return reject([cleanSqliteError(err), []]);
          }

          if (!row) {
            return resolve([null, null]);
          }

          resolve([null, row]);
        });
      } catch (e) {
        return reject(e);
      }
    });
  }

  static async Execute(sql, params = []) {
    return new Promise(function (resolve, reject) {
      try {
        const database = db.GetDatabase();

        database.run(sql, params, (err) => {
          database.close();

          if (err) {
            return reject(cleanSqliteError(err));
          }

          resolve(null);
        });
      } catch (e) {
        return reject(e);
      }
    });
  }
}

export { db };

Hopefully this helps. Here are links to Gists of them.

db.ts

db.js