A comprehensive Express.js framework for Node.js applications providing lazy API request handlers, authentication with OTP verification codes, role-based access control (RBAC), email notifications, push notifications and common tasks.
This is a port of django-nets-core for Node.js/Express applications.
-
🔐 Complete Authentication System
- OTP-based authentication with email verification codes
- OAuth2 token management (access & refresh tokens)
- Device management and tracking
- Session management with Redis
-
👥 Role-Based Access Control (RBAC)
- Flexible permission system
- Multi-project support with project-level roles
- Role inheritance and custom permissions
- Middleware for route protection
-
📧 Email System
- Template-based emails with Handlebars
- Queue support for batch sending
- Domain blacklist/whitelist
- HTML and text alternatives
- Customizable footers
-
📱 Push Notifications
- Firebase Cloud Messaging integration
- Device token management
- Batch notifications to user devices
- Error handling and token cleanup
-
🛡️ Security
- JWT token authentication
- CSRF protection
- Rate limiting
- Helmet security headers
- IP tracking and validation
-
🔧 Developer Experience
- TypeScript support
- Request parameter validation
- Automatic type conversion
- Comprehensive error handling
- Logging utilities
npm install nodejs-nets-core
# or
yarn add nodejs-nets-coreimport express from 'express';
import { NetsCoreApp, requestHandler, RequestParam } from 'nodejs-nets-core';
const app = express();
// Initialize nets-core
const netsCore = new NetsCoreApp({
database: {
dialect: 'postgres',
host: 'localhost',
port: 5432,
database: 'mydb',
username: 'user',
password: 'password',
},
redis: {
host: 'localhost',
port: 6379,
},
jwt: {
secret: 'your-secret-key',
accessTokenExpire: '30d',
},
});
// Apply middleware
netsCore.applyMiddleware(app);
// Define a protected route with parameter validation
app.post(
'/api/example',
requestHandler({
params: [
new RequestParam('name', 'string'),
new RequestParam('age', 'int', true), // optional
],
public: false, // requires authentication
}),
async (req, res) => {
const { name, age } = req.params;
res.json({ res: 1, data: { name, age } });
}
);
app.listen(3000, () => {
console.log('Server running on port 3000');
});// 1. Request verification code
app.post(
'/auth/login',
requestHandler({
params: [new RequestParam('email', 'email'), new RequestParam('device', 'object', true)],
public: true,
}),
authController.login
);
// 2. Authenticate with code
app.post(
'/auth/authenticate',
requestHandler({
params: [
new RequestParam('email', 'email'),
new RequestParam('code', 'string'),
new RequestParam('client_id', 'string'),
new RequestParam('client_secret', 'string'),
],
public: true,
}),
authController.authenticate
);// Step 1: Request verification code
const response = await fetch('/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'user@example.com',
device: {
name: 'iPhone 14',
os: 'iOS',
os_version: '17.0',
firebase_token: 'device-fcm-token',
},
}),
});
const { device_uuid } = await response.json();
// Step 2: Authenticate with code (received via email)
const authResponse = await fetch('/auth/authenticate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'user@example.com',
code: '123456',
client_id: 'your-client-id',
client_secret: 'your-client-secret',
device_uuid,
}),
});
const { access_token, refresh_token, user } = await authResponse.json();
// Use access_token for authenticated requests
fetch('/api/protected', {
headers: {
Authorization: `Bearer ${access_token}`,
},
});The requestHandler decorator provides automatic parameter validation and type conversion:
import { requestHandler, RequestParam } from 'nodejs-nets-core';
app.post(
'/api/create-post',
requestHandler({
params: [
new RequestParam('title', 'string'),
new RequestParam('content', 'string'),
new RequestParam('published', 'bool', true, { default: false }),
new RequestParam('publish_date', 'datetime', true),
new RequestParam('tags', 'list', true),
],
public: false, // requires authentication
canDo: 'blog.create_post', // permission check
}),
async (req, res) => {
// All parameters are validated and available in req.params
const { title, content, published, publish_date, tags } = req.params;
// req.user is automatically populated
// req.perm indicates if user has the required permission
res.json({ res: 1, data: 'Post created' });
}
);string/strint/integerfloat/numberbool/booleandatedatetimeemaillist/arrayobject/dictfile
import { Permission, Role } from 'nodejs-nets-core/models';
// Create permissions
const createPostPerm = await Permission.create({
codename: 'blog.create_post',
name: 'Can create blog posts',
description: 'Allows user to create new blog posts',
});
// Create role
const editorRole = await Role.create({
codename: 'editor',
name: 'Editor',
description: 'Blog editor role',
});
// Assign permission to role
await editorRole.addPermission(createPostPerm);// Method 1: Using canDo parameter
app.post(
'/api/posts',
requestHandler({
canDo: 'blog.create_post',
permRequired: true, // deny access if permission not granted
}),
createPostHandler
);
// Method 2: Using middleware
import { checkPermission } from 'nodejs-nets-core/middleware';
app.delete('/api/posts/:id', checkPermission('blog.delete_post'), deletePostHandler);
// Method 3: Role-based check
app.get(
'/api/admin',
requestHandler({
canDo: 'role:admin', // check if user has admin role
}),
adminHandler
);// Configure project models
netsCore.configure({
projectModel: 'Project',
projectMemberModel: 'ProjectMember',
});
// Routes automatically check project membership
app.post(
'/api/projects/:projectId/data',
requestHandler({
projectRequired: true,
canDo: 'project.edit_data',
}),
async (req, res) => {
// req.project - current project instance
// req.projectMembership - user's membership in project
// req.perm - permission check result
}
);import { sendEmail } from 'nodejs-nets-core/mail';
const [sent, reason, description] = await sendEmail({
subject: 'Welcome to our platform',
email: 'user@example.com', // or array of emails
template: 'emails/welcome.hbs',
context: {
username: 'John Doe',
activation_link: 'https://...',
},
toQueued: true, // queue for batch sending
});
if (!sent) {
console.error(`Email not sent: ${reason} - ${description}`);
}Create Handlebars templates in your templates directory:
netsCore.configure({
email: {
from: 'noreply@example.com',
excludeDomains: ['tempmail*', 'guerrillamail.com', '10minutemail.com'],
footer: '<p>© 2024 MyCompany</p>',
debugEnabled: false, // don't send emails in development
},
});netsCore.configure({
firebase: {
credentialsPath: './firebase-credentials.json',
},
});import { sendUserDeviceNotification } from 'nodejs-nets-core/firebase';
// Send to all user's devices
const results = await sendUserDeviceNotification(
user,
'New Message',
'You have a new message from John',
{
type: 'message',
messageId: '12345',
},
'messages' // notification channel
);
// Check results
Object.entries(results).forEach(([deviceId, result]) => {
if (result.success) {
console.log(`Sent to device ${deviceId}: ${result.message_id}`);
} else {
console.error(`Failed to send to device ${deviceId}: ${result.error}`);
}
});All models extend NetsCoreBaseModel which provides:
- Automatic timestamps (
created,updated) - Change tracking (
updated_fields) - JSON serialization (
toJSON()method) - Field protection (sensitive fields excluded from JSON)
import { NetsCoreBaseModel, OwnedModel } from 'nodejs-nets-core/models';
// NetsCoreBaseModel - timestamps only
class MyModel extends NetsCoreBaseModel {
// your fields
}
// OwnedModel - timestamps + user ownership
class MyOwnedModel extends OwnedModel {
// automatically has userId field
}- User - Base user model
- VerificationCode - OTP codes for authentication
- UserDevice - Device registration and tracking
- Permission - Permission definitions
- Role - Role definitions
- RolePermission - Role-permission associations
- UserRole - User-role assignments
- EmailTemplate - Reusable email templates
- CustomEmail - Queued emails
- EmailNotification - Email sending queue
- UserFirebaseNotification - Push notification history
Create a .env file:
# Database
DB_DIALECT=postgres
DB_HOST=localhost
DB_PORT=5432
DB_NAME=mydb
DB_USER=user
DB_PASSWORD=password
# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
# JWT
JWT_SECRET=your-secret-key-change-in-production
ACCESS_TOKEN_EXPIRE=30d
# Email
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASSWORD=your-password
DEFAULT_FROM_EMAIL=noreply@example.com
# Firebase
FIREBASE_CONFIG=./firebase-credentials.json
# App
NODE_ENV=development
PORT=3000const netsCore = new NetsCoreApp({
database: {
dialect: 'postgres',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
database: process.env.DB_NAME,
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
logging: false,
},
redis: {
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT),
},
jwt: {
secret: process.env.JWT_SECRET,
accessTokenExpire: process.env.ACCESS_TOKEN_EXPIRE || '30d',
},
email: {
host: process.env.SMTP_HOST,
port: parseInt(process.env.SMTP_PORT),
secure: false,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASSWORD,
},
from: process.env.DEFAULT_FROM_EMAIL,
excludeDomains: ['tempmail*'],
debugEnabled: false,
},
firebase: {
credentialsPath: process.env.FIREBASE_CONFIG,
},
verificationCode: {
expireSeconds: 15 * 60, // 15 minutes
debugCode: '123456',
testersEmails: ['google_tester*'],
testersCode: '789654',
},
security: {
globalProtectedFields: ['password', 'token', 'secret'],
rateLimitWindowMs: 15 * 60 * 1000,
rateLimitMax: 100,
},
projectModel: 'Project', // optional for multi-project support
projectMemberModel: 'ProjectMember', // optional
});interface RequestHandlerOptions {
// Model to load based on index field
model?: any;
// Field to use as identifier (default: 'id')
indexField?: string;
// Permission to check
canDo?: string;
// Require permission (deny if not granted)
permRequired?: boolean;
// Request parameters to validate
params?: RequestParam[];
// Allow unauthenticated access
public?: boolean;
// Require project context
projectRequired?: boolean;
}class RequestParam {
constructor(
key: string, // Parameter name
type: ParamType, // Data type
optional?: boolean, // Is optional?
options?: {
default?: any; // Default value
validate?: Function; // Custom validator
filetypes?: string[]; // For file uploads
}
);
}import {
successResponse,
errorResponse,
notFoundResponse,
permissionDenied,
} from 'nodejs-nets-core/responses';
// Success with data
return successResponse({ user: userData });
// Success with extra data
return successResponse(userData, { token: 'abc123' });
// Error
return errorResponse('Invalid input', 400);
// Not found
return notFoundResponse();
// Permission denied
return permissionDenied();Configure tester emails that bypass OTP verification:
netsCore.configure({
verificationCode: {
testersEmails: ['google_tester*', 'test@example.com'],
testersCode: '475638', // static code for testers
},
});Emails matching tester patterns will always use the configured code instead of generating random ones.
If you're migrating from django-nets-core:
- Models: Sequelize models map directly to Django models
- Decorators:
@request_handlerbecomesrequestHandler() - Permissions: Same codename system
- Email: Similar API with template support
- Firebase: Same Firebase Admin SDK
See MIGRATION.md for detailed migration guide.
Check the /examples directory for:
- Basic authentication setup
- Role-based access control
- Email notifications
- Push notifications
- Multi-project applications
MIT
Contributions are welcome! Please read CONTRIBUTING.md for details.
- GitHub Issues: https://github.com/yourusername/nodejs-nets-core/issues
- Documentation: https://nodejs-nets-core.readthedocs.io
Port of django-nets-core by esbozos.