SQLKit
A Promise based Database Access library for PostgreSQL
Motivation
This library is created to provide a simple yet powerful data access layer for
performing common database query tasks without the need to incorporate a large
ORM in a project.
Implementation
SQLKit internally uses the npm pg package. By default connections are created
using a connection pool. SQLKit supports multiple connection pools for performing
database queries on multiple databases from the same application.
SQLKit dynamically creates a Promise function for a given SQL query. This function
can then be used in your application for database manipulation.
Additionally SQLKit uses the yesql library for handling named query parameters:
const query = "SELECT * FROM employee WHERE id = :employee_id";
Installation
For yarn
yarn add @blendsdk/sqlkit pg @types/pg --save
For npm
npm install @blendsdk/@sqlkit pg @types/pg --save
Usage
Create a connection
SQLKit internally uses the pooling mechanism of the pg library. The createConnection function
requires a configuration object to function. SQLKit keeps track of the connection pool if you need
connections to multiple databases. This is done by taking a unique connection name as parameter.
This parameter is optional and if not provided it defaults to default
If you have an application that connects to a single database, then you just need to call
createConnection once at the startup of your application.
createConnection({
host: "localhost",
database: "mydatabase",
user: "postgres",
password: "postgres"
});
Creating a connection using environment variables.
A recommended way to configure and create a connection is to make use of environment variables
as configuration parameters. In NodeJS this is easily accomplished using the dotenv package.
You just need to create a .env file and let the library take care of the rest.
DB_HOST=localhost
DB_USER=myuser
DB_PASSWORD=secret
DB_DATABASE=mydb
import dotenv from "dotenv";
dotenv.config({ path: ".env" });
createConnection();
You also can create a connection using static configuration.
createConnection({
host: "localhost",
database: "mydb",
user: "myuser",
password: "secret",
port: 5432
});
Create an arbitrary SQL statement
SQLKit can wrap any SQL statement as an asynchronous JS function. Here are several examples:
import { sql_query } from "@blendsdl/sqlkit";
interface IPerson {
person_id?: number;
first_name?: string;
last_name?: string;
}
const getAllPersons = sql_query<IPerson[], unknown>("SELECT * FROM tbl_person");
const persons = await getAllPersons();
Query options
You can pass configuration options to SQLKit to configure the function that is created
which runs your SQL query.
export interface IQueryOptions<InputType, OutputType> {
single?: boolean;
inConverter?: (record: InputType) => any;
outConverter?: (record: OutputType) => any;
}
Create an INSERT statement
SQLKit comes with an INSERT statement function that can insert single records
into a table. In this example we will insert a record into the tbl_person table
import { sql_insert } from "@blendsdk/sqlkit";
const createNewPerson = sql_insert<IPerson, IPerson>("tbl_person");
const newPerson = await createNewPerson({
first_name: "John",
last_name: "Doe"
});
console.log(newPerson);
Create an UPDATE statement
SQLKit also includes an UPDATE statement that can be used to update one or more records
from a table.
interface IPersonLastName {
last_name: string;
}
interface IPersonID {
person_id: number;
}
const updatePersonLastNameByID = sql_update<
IPerson,
IPersonLastName,
IPersonID
>("tbl_person", {
single: true
});
const result = await updatePersonLastNameByID(
{
last_name: "Peters"
},
{
person_id: 1
}
);
Create a DELETE statement
SQLKit also includes an DELETE statement that can be used to delete one or more records
from a table.
interface IPersonFirstAndLastName {
fist_name: string;
last_name: string;
}
const deleteByFirstAndLastName = sql_delete<IPerson, IPersonFirstAndLastName>(
"tbl_person"
);
const deletedRecords = deleteByFirstAndLastName({
fist_name: "Jane",
last_name: "Peters"
});
A COUNT of records example.
interface IOpenOrders {
num_open_orders: number;
}
interface IOrderOrdersCustomerID {
customer_id: number;
}
const countOpenOrdersByCustomerID = sql_query<
IOpenOrders,
IOrderOrdersCustomerID
>(`
SELECT
COUNT(*) AS num_open_orders
FROM
tbl_order
WHERE
is_closed = false
customer_id = :customer_id
`);
const result = await countOpenOrdersByCustomerID({
customer_id: 30
});
Converting values before and after query
In this example I would like to show use how to serialize and
de-serialize a JSON object into a text field with a database table
using the conversion methods in SQLKit.
DROP TABLE IF EXISTS tbl_document;
CREATE TABLE tbl_document (
document_id serial not null primary key,
content text not null,
meta_data text
);
interface IDocumentMetaData {
author: string;
tags: string[];
}
interface IDocument {
document_id?: number;
content?: string;
meta_data?: IDocumentMetaData;
}
const documentInConverter = (record: IDocument): IDocument => {
return {
...record,
meta_data: JSON.stringify(record.meta_data || {}) as any
};
};
const documentOutConverter = (record: IDocument): IDocument => {
return {
...record,
meta_data: JSON.parse((record.meta_data as any) || "{}")
};
};
const createNewDocument = sql_insert<IDocument, IDocument>("tbl_document", {
inConverter: documentInConverter,
outConverter: documentOutConverter
});
const newDocument = createNewDocument({
content: "This is some content",
meta_data: {
author: "Some Person",
tags: ["tag1", "tag2", "tag3"]
}
});
Logging with Winston
SQLKit can log the SQL statements using the winston logger. In order to do that
you need to have logger configures and ready to go, then assign the logger to SQLKit
using the registerDatabaseLogger function:
const logger = winston.createLogger({
transports: [
new winston.transports.Console({
level: process.env.NODE_ENV === "production" ? "error" : "debug"
})
]
});
registerDatabaseLogger(logger);