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:
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:
# 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: ...}