๐Ÿ“ฆ Model Redis

Simple ORM for Redis in Node.js

Redis ORM Made Simple

Schema-based validation, automatic type conversion, and model relationships for Redis in Node.js. Perfect for small to medium-sized projects that need a simple data layer.

๐Ÿ“‹

Schema Validation

Type checking, min/max values, and required fields

๐Ÿ”—

Relationships

One-to-one and one-to-many with cycle detection

๐Ÿ”„

Type Conversion

Automatic conversion between Redis strings and native types

๐Ÿงช

Well Tested

84%+ code coverage with comprehensive test suite

Installation

npm install model-redis

Quick Start

const {setUpTable} = require('model-redis');

// Set up the table with optional config
const Table = await setUpTable({
    prefix: 'myapp:'  // Optional key prefix
});

// Define your model
class User extends Table {
    static _key = 'username';
    static _keyMap = {
        username: {type: 'string', isRequired: true, min: 3, max: 50},
        email: {type: 'string', isRequired: true},
        age: {type: 'number', min: 0, max: 150},
        active: {type: 'boolean', default: true},
        created: {type: 'number', default: () => Date.now()}
    };
}

// Use it!
const user = await User.create({
    username: 'john',
    email: 'john@example.com',
    age: 30
});

console.log(user.username); // 'john'
console.log(user.active);   // true (default value)

// Query
const john = await User.get('john');
const allUsers = await User.listDetail();
const exists = await User.exists('john'); // true

// Update
await john.update({age: 31});

// Delete
await john.remove();

Schema Definition

Define your data structure with a simple schema:

type

Data type: string, number, boolean, or object

isRequired

Field must be provided when creating entries

default

Default value or function to generate default

min / max

Minimum/maximum value for numbers or string length

always

Always apply default on update (e.g., timestamps)

isPrivate

Exclude from JSON output (e.g., passwords)

Model Relationships

Define relationships between models with automatic loading and cycle detection:

// Define models with relationships
class User extends Table {
    static _key = 'id';
    static _keyMap = {
        id: {type: 'string', isRequired: true},
        name: {type: 'string', isRequired: true},
        posts: {model: 'Post', rel: 'many', remoteKey: 'userId', localKey: 'id'}
    };
}

class Post extends Table {
    static _key = 'id';
    static _keyMap = {
        id: {type: 'string', isRequired: true},
        title: {type: 'string', isRequired: true},
        userId: {type: 'string', isRequired: true},
        user: {model: 'User', rel: 'one', localKey: 'userId'}
    };
}

// Register models for relationships
User.register();
Post.register();

// Relationships are loaded automatically
const user = await User.get('user1');
console.log(user.posts); // Array of Post instances

const post = await Post.get('post1');
console.log(post.user);  // User instance

๐Ÿ”„ Automatic Cycle Detection

The QueryHelper class prevents infinite loops in circular relationships. When loading a User with Posts, each Post's User reference won't recurse infinitely.

API Reference

Static Methods

await Model.create(data)

Create a new entry with validation

await Model.get(pk)

Get entry by primary key

await Model.exists(pk)

Check if entry exists (returns boolean)

await Model.list()

Get array of all primary keys

await Model.listDetail(options)

Get array of all instances with optional filtering

await Model.findall(options)

Alias for listDetail()

Model.register()

Register model for relationships

Instance Methods

await instance.update(data)

Update instance with new data

await instance.remove()

Delete the instance from Redis

instance.toJSON()

Convert to plain object (excludes isPrivate fields)

instance.toString()

Returns primary key value as string

Error Handling

try {
    await User.create({username: 'ab'}); // Too short (min: 3)
} catch(error) {
    if(error.name === 'ObjectValidateError') {
        console.log(error.message); // Array of validation errors
        console.log(error.status);  // 422
    }
}

try {
    await User.get('nonexistent');
} catch(error) {
    if(error.name === 'EntryNotFound') {
        console.log(error.status);  // 404
    }
}

try {
    await User.create({username: 'john', email: 'john@example.com'});
    await User.create({username: 'john', email: 'other@example.com'});
} catch(error) {
    if(error.name === 'EntryNameUsed') {
        console.log(error.status);  // 409
    }
}

Testing

Model Redis comes with a comprehensive test suite:

84.88%
Code Coverage
67
Total Tests
66
Passing
# Run tests
npm test

# Watch mode
npm run test:watch

# Coverage report
npm run test:coverage

Complete Example

const {setUpTable} = require('model-redis');
const bcrypt = require('bcrypt');

const Table = await setUpTable({prefix: 'auth:'});

class User extends Table {
    static _key = 'username';
    static _keyMap = {
        username: {type: 'string', isRequired: true, min: 3, max: 50},
        password: {type: 'string', isRequired: true, min: 8, isPrivate: true},
        email: {type: 'string', isRequired: true},
        createdAt: {type: 'number', default: () => Date.now()},
        updatedAt: {type: 'number', default: () => Date.now(), always: true}
    };

    static async create(data) {
        // Hash password before saving
        data.password = await bcrypt.hash(data.password, 10);
        return await super.create(data);
    }

    async checkPassword(password) {
        return await bcrypt.compare(password, this.password);
    }

    static async login(username, password) {
        const user = await User.get(username);
        const valid = await user.checkPassword(password);

        if (!valid) {
            throw new Error('Invalid credentials');
        }

        return user;
    }
}

// Usage
const user = await User.create({
    username: 'john',
    password: 'secretpass',
    email: 'john@example.com'
});

// Login
const loggedIn = await User.login('john', 'secretpass');

// Password is hidden in JSON
console.log(loggedIn.toJSON());
// {username: 'john', email: 'john@example.com', createdAt: ..., updatedAt: ...}