RDB
The ultimate ORM for Node and Typescript
README
RDB
RDB is the ultimate Object Relational Mapper for Node.js and Typescript, offering seamless integration with popular databases like Postgres, MS SQL, MySQL, Sybase SAP, and SQLite. Whether you're building applications in TypeScript or JavaScript (including both CommonJS and ECMAScript), RDB has got you covered.
Key Features
- Rich Querying Model: RDB provides a powerful and intuitive querying model, making it easy to retrieve, filter, and manipulate data from your databases.
- Concise API: With a concise and developer-friendly API, RDB enables you to interact with your database using simple and expressive syntax.
- No Code Generation Required: Enjoy full IntelliSense, even in table mappings, without the need for cumbersome code generation.
- TypeScript and JavaScript Support: RDB fully supports both TypeScript and JavaScript, allowing you to leverage the benefits of static typing and modern ECMAScript features.
- Works in the Browser: You can securely use RDB in the browser by utilizing the Express.js plugin, which serves to safeguard sensitive database credentials from exposure at the client level. This method mirrors a traditional REST API, augmented with advanced TypeScript tooling for enhanced functionality.
This is the _Modern Typescript Documentation_. Are you looking for the _Classic Documentation_ ?
Installation
- ```bash
- $ npm install rdb
- ```
Example
Here we choose SQLite.
- ```bash
- $ npm install sqlite3
- ```
- ```javascript
- import rdb from 'rdb';
- const map = rdb.map(x => ({
- customer: x.table('customer').map(({ column }) => ({
- id: column('id').numeric().primary().notNullExceptInsert(),
- name: column('name').string(),
- balance: column('balance').numeric(),
- isActive: column('isActive').boolean(),
- })),
- order: x.table('_order').map(({ column }) => ({
- id: column('id').numeric().primary().notNullExceptInsert(),
- orderDate: column('orderDate').date().notNull(),
- customerId: column('customerId').numeric().notNullExceptInsert(),
- })),
- orderLine: x.table('orderLine').map(({ column }) => ({
- id: column('id').numeric().primary().notNullExceptInsert(),
- orderId: column('orderId').numeric(),
- product: column('product').string(),
- })),
- deliveryAddress: x.table('deliveryAddress').map(({ column }) => ({
- id: column('id').numeric().primary().notNullExceptInsert(),
- orderId: column('orderId').numeric(),
- name: column('name').string(),
- street: column('street').string(),
- postalCode: column('postalCode').string(),
- postalPlace: column('postalPlace').string(),
- countryCode: column('countryCode').string(),
- }))
- })).map(x => ({
- order: x.order.map(v => ({
- customer: v.references(x.customer).by('customerId'),
- lines: v.hasMany(x.orderLine).by('orderId'),
- deliveryAddress: hasOne(x.deliveryAddress).by('orderId'),
- }))
- }));
- export default map;
- ```
- ```javascript
- import map from './map';
- const db = map.sqlite('demo.db');
- updateRow();
- async function updateRow() {
- const order = await db.order.getById(2, {
- lines: true
- });
- order.lines.push({
- product: 'broomstick'
- });
- await order.saveChanges();
- }
- ```
API
Mapping tables
To define a mapping, you employ the map() method, linking your tables and columns to corresponding object properties. You provide a callback function that engages with a parameter representing a database table.- ```javascript
- import rdb from 'rdb';
- const map = rdb.map(x => ({
- customer: x.table('customer').map(({ column }) => ({
- id: column('id').numeric().primary().notNullExceptInsert(),
- name: column('name').string(),
- balance: column('balance').numeric(),
- isActive: column('isActive').boolean(),
- })),
- order: x.table('_order').map(({ column }) => ({
- id: column('id').numeric().primary().notNullExceptInsert(),
- orderDate: column('orderDate').date().notNull(),
- customerId: column('customerId').numeric().notNullExceptInsert(),
- })),
- orderLine: x.table('orderLine').map(({ column }) => ({
- id: column('id').numeric().primary().notNullExceptInsert(),
- orderId: column('orderId').numeric(),
- product: column('product').string(),
- })),
- deliveryAddress: x.table('deliveryAddress').map(({ column }) => ({
- id: column('id').numeric().primary().notNullExceptInsert(),
- orderId: column('orderId').numeric(),
- name: column('name').string(),
- street: column('street').string(),
- postalCode: column('postalCode').string(),
- postalPlace: column('postalPlace').string(),
- countryCode: column('countryCode').string(),
- }))
- })).map(x => ({
- order: x.order.map(({ hasOne, hasMany, references }) => ({
- customer: references(x.customer).by('customerId'),
- deliveryAddress: hasOne(x.deliveryAddress).by('orderId'),
- lines: hasMany(x.orderLine).by('orderId')
- }))
- }));
- export default map;
- ```
The init.js script resets our SQLite database. It's worth noting that SQLite databases are represented as single files, which makes them wonderfully straightforward to manage.
At the start of the script, we import our database mapping from the map.js file. This gives us access to the db object, which we'll use to interact with our SQLite database.
Then, we define a SQL string. This string outlines the structure of our SQLite database. It first specifies to drop existing tables named 'deliveryAddress', 'orderLine', '_order', and 'customer' if they exist. This ensures we have a clean slate. Then, it dictates how to create these tables anew with the necessary columns and constraints.
Because of a peculiarity in SQLite, which only allows one statement execution at a time, we split this SQL string into separate statements. We do this using the split() method, which breaks up the string at every semicolon.
- ```javascript
- import map from './map';
- const db = map.sqlite('demo.db');
- const sql = `DROP TABLE IF EXISTS deliveryAddress; DROP TABLE IF EXISTS orderLine; DROP TABLE IF EXISTS _order; DROP TABLE IF EXISTS customer;
- CREATE TABLE customer (
- id INTEGER PRIMARY KEY,
- name TEXT,
- balance NUMERIC,
- isActive INTEGER
- );
- CREATE TABLE _order (
- id INTEGER PRIMARY KEY,
- orderDate TEXT,
- customerId INTEGER REFERENCES customer
- );
- CREATE TABLE orderLine (
- id INTEGER PRIMARY KEY,
- orderId INTEGER REFERENCES _order,
- product TEXT
- );
- CREATE TABLE deliveryAddress (
- id INTEGER PRIMARY KEY,
- orderId INTEGER REFERENCES _order,
- name TEXT,
- street TEXT,
- postalCode TEXT,
- postalPlace TEXT,
- countryCode TEXT
- )
- `;
- async function init() {
- const statements = sql.split(';');
- for (let i = 0; i < statements.length; i++) {
- await db.query(statements[i]);
- }
- }
- export default init;
- ```
In SQLite, columns with the INTEGER PRIMARY KEY attribute are designed to autoincrement by default. This means that each time a new record is inserted into the table, SQLite automatically produces a numeric key for the id column that is one greater than the largest existing key. This mechanism is particularly handy when you want to create unique identifiers for your table rows without manually entering each id.
Connecting
__SQLite__
- ```bash
- $ npm install sqlite3
- ```
- ```javascript
- import map from './map';
- const db = map.sqlite('demo.db');
- ```
__With connection pool__
- ```bash
- $ npm install sqlite3
- ```
- ```javascript
- import map from './map';
- const db = map.sqlite('demo.db', { size: 10 });
- ```
__From the browser__
You can securely use RDB from the browser by utilizing the Express.js plugin, which serves to safeguard sensitive database credentials from exposure at the client level. This technique bypasses the need to transmit raw SQL queries directly from the client to the server. Instead, it logs method calls initiated by the client, which are later replayed and authenticated on the server. This not only reinforces security by preventing the disclosure of raw SQL queries on the client side but also facilitates a smoother operation. Essentially, this method mirrors a traditional REST API, augmented with advanced TypeScript tooling for enhanced functionality. You can read more about it in the section called In the browser
📄 server.js- ```javascript
- import map from './map';
- import { json } from 'body-parser';
- import express from 'express';
- import cors from 'cors';
- const db = map.sqlite('demo.db');
- express().disable('x-powered-by')
- .use(json({ limit: '100mb' }))
- .use(cors())
- //for demonstrational purposes, authentication middleware is not shown here.
- .use('/rdb', db.express())
- .listen(3000, () => console.log('Example app listening on port 3000!'));
- ```
- ```javascript
- import map from './map';
- const db = map.http('http://localhost:3000/rdb');
- ```
__MySQL__
- ```bash
- $ npm install mysql2
- ```
- ```javascript
- import map from './map';
- const db = map.mysql('mysql://test:test@mysql/test');
- ```
__MS SQL__
- ```bash
- $ npm install tedious
- ```
- ```javascript
- import map from './map';
- const db = map.mssql({
- server: 'mssql',
- options: {
- encrypt: false,
- database: 'test'
- },
- authentication: {
- type: 'default',
- options: {
- userName: 'sa',
- password: 'P@assword123',
- }
- }
- });
- ```
__PostgreSQL__
- ```bash
- $ npm install pg
- ```
- ```javascript
- import map from './map';
- const db = map.pg('postgres://postgres:postgres@postgres/postgres');
- ```
__SAP Adaptive Server__
- ```bash
- $ npm install msnodesqlv8
- ```
- ```javascript
- import { fileURLToPath } from 'url';
- import { dirname } from 'path';
- import map from './map';
- const __filename = fileURLToPath(import.meta.url);
- const __dirname = dirname(__filename);
- //download odbc driver from sap web pages
- const db = map.pg(`Driver=${__dirname}/libsybdrvodb.so;SERVER=sapase;Port=5000;UID=sa;PWD=sybase;DATABASE=test`);
- ```