ods-utils
v1.13.0
Published
Shared utilities for microservices architecture
Maintainers
Readme
ODS Utils
A comprehensive collection of shared utilities. This package provides standardized solutions for common tasks in Node.js microservices, helping teams maintain consistency and reduce code duplication.
Table of Contents
Features
- Configuration Management: Centralized configuration with environment variable support
- Structured Logging: Consistent logging format across services
- Caching: Multiple caching providers (Redis, in-memory) with support for custom providers
- Database Connectivity: MongoDB and PostgreSQL connection management and utilities
- Response Handling: Framework-agnostic standardized API responses (Express, Koa, Fastify, Hapi, and custom adapters)
- Authentication: JWT-based authentication middleware for Express
- Rate Limiting: Configurable rate limiting for API protection
- Date Utilities: Comprehensive date manipulation, formatting, parsing, and comparison utilities
Installation
npm install ods-utilsUsage
Configuration
Load and access configuration values from environment variables:
const { config } = require("ods-utils");
// Load configuration
const appConfig = config.load();
console.log(appConfig.port); // Access configuration valuesLogging
Structured logging with different log levels:
const { logger } = require("ods-utils");
logger.info("This is an info message");
logger.error("This is an error message", { userId: "123", errorCode: "AUTH_FAILED" });
logger.debug("Debug information", { requestId: "req-123" });Caching
The caching module supports multiple providers (Redis, in-memory) and allows you to create custom providers.
Redis Provider (Default)
const { cache } = require("ods-utils");
// Initialize Redis cache with project-specific configuration
cache.init({
redis: {
url: process.env.REDIS_URL || "redis://localhost:6379",
username: process.env.REDIS_USERNAME,
password: process.env.REDIS_PASSWORD,
pool: {
min: parseInt(process.env.REDIS_POOL_MIN) || 1,
max: parseInt(process.env.REDIS_POOL_MAX) || 10,
}
}
});
// Set a value in the cache
await cache.set("key", { value: "data" }, 3600); // TTL in seconds
// Get a value from the cache
const value = await cache.get("key");
// Delete a value from the cache
await cache.del("key");
// Clear the entire cache
await cache.clear();In-Memory Provider
const { cache } = require("ods-utils");
const { MemoryProvider } = cache.providers;
// Initialize with in-memory provider
cache.init({
provider: new MemoryProvider(),
memory: {
maxSize: 100, // Maximum number of items to store
cleanupInterval: 30000 // Cleanup interval in milliseconds (30 seconds)
}
});
// Use the cache with the same API as Redis provider
await cache.set("key", { value: "data" }, 3600);
const value = await cache.get("key");
await cache.del("key");
await cache.clear();Custom Provider
You can create your own cache provider by extending the CacheProvider base class:
const { cache } = require("ods-utils");
const { CacheProvider } = cache.providers;
// Create a custom provider
class MyCustomProvider extends CacheProvider {
constructor() {
super();
this.data = {}; // Your storage mechanism
}
init(options) {
// Initialize your provider with options
console.log("Custom provider initialized with options:", options.custom || {});
}
async set(key, value, ttl) {
// Implement set logic
this.data[key] = value;
return true;
}
async get(key) {
// Implement get logic
return this.data[key] || null;
}
async del(key) {
// Implement delete logic
delete this.data[key];
return true;
}
async clear() {
// Implement clear logic
this.data = {};
return true;
}
}
// Initialize with your custom provider
cache.init({
provider: new MyCustomProvider(),
custom: {
// Your custom options
someSetting: "value"
}
});
// Use the cache with the same API
await cache.set("key", { value: "data" });
const value = await cache.get("key");Database
Database connection management and utilities for MongoDB and PostgreSQL:
MongoDB
const { db } = require("ods-utils");
// Initialize MongoDB connection
await db.mongodb.init({
mongodb: {
uri: process.env.MONGODB_URI || "mongodb://localhost:27017",
dbName: process.env.MONGODB_DB_NAME || "myapp",
options: {
maxPoolSize: 10,
minPoolSize: 1,
connectTimeoutMS: 5000,
}
}
});
// Find documents in a collection
const users = await db.mongodb.find("users", { active: true });
// Find a single document
const user = await db.mongodb.findOne("users", { email: "[email protected]" });
// Insert a document
const result = await db.mongodb.insertOne("users", {
name: "John Doe",
email: "[email protected]",
createdAt: new Date()
});
// Update a document
await db.mongodb.updateOne(
"users",
{ email: "[email protected]" },
{ $set: { lastLogin: new Date() } }
);
// Delete a document
await db.mongodb.deleteOne("users", { email: "[email protected]" });
// Perform an aggregation
const stats = await db.mongodb.aggregate("users", [
{ $match: { active: true } },
{ $group: { _id: "$role", count: { $sum: 1 } } }
]);
// Close the connection when your application shuts down
await db.mongodb.close();PostgreSQL
const { db } = require("ods-utils");
// Initialize PostgreSQL connection pool
db.postgresql.init({
postgresql: {
connectionString: process.env.DATABASE_URL,
// Or provide individual connection parameters
host: process.env.DB_HOST || "localhost",
port: process.env.DB_PORT || 5432,
database: process.env.DB_NAME || "myapp",
user: process.env.DB_USER || "postgres",
password: process.env.DB_PASSWORD,
max: 10, // Maximum number of clients in the pool
idleTimeoutMillis: 30000,
}
});
// Execute a simple query
const result = await db.postgresql.query(
"SELECT * FROM users WHERE active = $1",
[true]
);
console.log(result.rows);
// Find rows in a table
const users = await db.postgresql.find("users", { active: true });
// Find a single row
const user = await db.postgresql.findOne("users", { email: "[email protected]" });
// Insert a row
const newUser = await db.postgresql.insert("users", {
name: "Jane Doe",
email: "[email protected]",
created_at: new Date()
});
// Update rows
const updatedUsers = await db.postgresql.update(
"users",
{ email: "[email protected]" },
{ last_login: new Date() }
);
// Delete rows
const deletedUsers = await db.postgresql.remove("users", { email: "[email protected]" });
// Execute a transaction
await db.postgresql.transaction(async (client) => {
await client.query("UPDATE accounts SET balance = balance - $1 WHERE user_id = $2", [100, 1]);
await client.query("UPDATE accounts SET balance = balance + $1 WHERE user_id = $2", [100, 2]);
});
// Close the connection pool when your application shuts down
await db.postgresql.close();Response Handling
Framework-agnostic standardized API responses with built-in support for Express, Koa, Fastify, and Hapi:
const { responseHandler } = require("ods-utils");
// Express.js example
app.get("/api/users", (req, res) => {
responseHandler.sendSuccess(res, {
message: "Users retrieved successfully",
data: users
});
});
// Koa.js example
router.get("/api/users", async (ctx) => {
responseHandler.sendSuccess(ctx, {
message: "Users retrieved successfully",
data: users
});
});
// Fastify example
fastify.get("/api/users", async (request, reply) => {
responseHandler.sendSuccess(reply, {
message: "Users retrieved successfully",
data: users
});
});
// Hapi example
server.route({
method: "GET",
path: "/api/users",
handler: async (request, h) => {
return responseHandler.sendSuccess(h, {
message: "Users retrieved successfully",
data: users
});
}
});
// Error handling
app.use((err, req, res, next) => {
responseHandler.sendServerError(res, {
message: "An error occurred",
error: err
});
});Custom Adapters
You can also create custom adapters for other frameworks:
const { responseHandler } = require("ods-utils");
// Create a custom adapter
const customAdapter = (res, statusCode, responseBody) => {
// Custom implementation for your framework
return res.writeHead(statusCode, { "Content-Type": "application/json" })
.end(JSON.stringify(responseBody));
};
// Use the custom adapter
responseHandler.sendSuccess(res, {
message: "Success",
data: result,
adapter: customAdapter
});
// Or add it to the adapters collection for reuse
responseHandler.adapters.customFramework = customAdapter;
// Then use it by name
responseHandler.sendSuccess(res, {
message: "Success",
data: result,
adapter: responseHandler.adapters.customFramework
});Date Utilities
Comprehensive date manipulation, formatting, parsing, and comparison utilities:
const { dateutils } = require("ods-utils");
// Format date with default format (YYYY-MM-DD)
const formattedDate = dateutils.formatDate(new Date());
console.log("Formatted date:", formattedDate);
// Format date with custom format
console.log("Custom format:", dateutils.formatDate(new Date(), "DD/MM/YYYY"));
console.log("With time:", dateutils.formatDate(new Date(), "YYYY-MM-DD HH:mm:ss"));
// Parse date strings
const parsedDate1 = dateutils.parseDate("2023-05-15");
const parsedDate2 = dateutils.parseDate("05/15/2023", "MM/DD/YYYY");
const parsedDate3 = dateutils.parseDate("15/05/2023", "DD/MM/YYYY");
// Validate date strings
console.log("Is valid date:", dateutils.isValidDate("2023-05-15")); // true
console.log("Is valid date:", dateutils.isValidDate("not-a-date")); // false
// Add time to date
const futureDate = dateutils.addTime(new Date(), 5, "days");
const nextMonth = dateutils.addTime(new Date(), 1, "month");
const nextYear = dateutils.addTime(new Date(), 1, "year");
// Subtract time from date
const pastDate = dateutils.subtractTime(new Date(), 5, "days");
const lastMonth = dateutils.subtractTime(new Date(), 1, "month");
// Compare dates
const comparison = dateutils.compareDate(new Date(), new Date(2022, 0, 1));
// Returns: 1 (first date is after second date)
// Calculate difference between dates
const diffDays = dateutils.dateDiff(new Date(2023, 0, 1), new Date(2023, 0, 15), "days");
// Returns: 14
// Get date boundaries
const startOfDay = dateutils.startOf(new Date(), "day");
const endOfDay = dateutils.endOf(new Date(), "day");
const startOfMonth = dateutils.startOf(new Date(), "month");
const endOfMonth = dateutils.endOf(new Date(), "month");
// Convenience functions
const now = dateutils.now(); // Current date and time
const today = dateutils.today(); // Today at 00:00:00
const tomorrow = dateutils.tomorrow(); // Tomorrow at 00:00:00
const yesterday = dateutils.yesterday(); // Yesterday at 00:00:00
// Get relative time description
const relativeTime = dateutils.getRelativeTime(new Date(2023, 0, 1), new Date());
// Returns: "5 months ago" (example)
// Format uptime in seconds to a human-readable string
const uptime = process.uptime();
const formattedUptime = dateutils.formatUptime(uptime);
// Returns: "2d 5h 30m 15s" (example)Middleware
Common Express middleware functions from various modules:
const { authentication, rateLimiter, logging, responseHandler, constants } = require("ods-utils");
// Apply authentication middleware
app.use(authentication());
// Apply rate limiting middleware
// Uses constants.DEFAULT_RATE_LIMIT_WINDOW_MS (60000ms = 1 minute) as default window
app.use(rateLimiter());
// Apply rate limiting middleware with custom options
app.use(rateLimiter({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: "Too many requests, please try again later"
}));
// Apply request logging middleware (if available)
// Note: Check the documentation for the latest implementation
// Apply error handling middleware (if available)
// Note: Check the documentation for the latest implementationArchitecture
ODS Utils is designed with a modular architecture to provide flexibility and maintainability:
- config: Handles loading and validating configuration from environment variables
- logging: Provides structured logging with Winston
- caching: Implements multiple caching strategies (Redis, in-memory) with a provider-based architecture for extensibility
- database: Manages connections and operations for MongoDB and PostgreSQL
- responseHandler: Standardizes API responses and error handling
- authentication: Provides JWT-based authentication middleware
- rateLimiter: Implements rate limiting to protect APIs from abuse
- dateutils: Provides comprehensive date manipulation, formatting, parsing, and comparison utilities
Examples
Check out the examples directory for examples of how to use this package in your projects. The examples include:
- Express application with all utilities integrated
- Framework-agnostic response handlers with Express, Koa, Fastify, Hapi, and custom adapters
- Configuration management examples
- Logging patterns
- Caching strategies with different providers (Redis, in-memory, custom)
- Database connectivity with MongoDB and PostgreSQL
- API response standardization
- Authentication and rate limiting middleware usage
- Date utilities for formatting, parsing, manipulation, and comparison
Development
To set up the development environment:
# Clone the repository
git clone https://gitlab.com/your-organization/ods-utils.git
# Install dependencies
cd ods-utils
npm install
# Run tests
npm testPublishing to npm
To publish this package to the npm registry:
# Login to npm (you need an npm account)
npm login
# Run tests to ensure everything is working
npm test
# Bump the version (choose one of the following)
npm version patch # for bug fixes
npm version minor # for new features
npm version major # for breaking changes
# Publish to npm
npm publishNote: Before publishing, make sure you have the necessary permissions to publish to the npm registry under the package name "ods-utils". If the package name is already taken, you may need to choose a different name by updating the "name" field in package.json.
Testing
The project uses Jest as the testing framework. Tests are located in the tests directory and are organized by module:
tests/authentication.test.js: Tests for the authentication moduletests/caching.test.js: Tests for the caching module (including different providers and provider architecture)tests/config.test.js: Tests for the configuration moduletests/responseHandler.test.js: Tests for the response handler moduletests/logging.test.js: Tests for the logging moduletests/mongodb.test.js: Tests for the MongoDB database moduletests/postgresql.test.js: Tests for the PostgreSQL database moduletests/rateLimiter.test.js: Tests for the rate limiter module
To run the tests:
# Run all tests
npm test
# Run tests with coverage report
npm test -- --coverage
# Run tests for a specific module
npm test -- tests/caching.test.jsThe tests use mocking to isolate the modules and test them independently without requiring external services like Redis, MongoDB, or PostgreSQL.
Test Coverage
The test suite aims to provide comprehensive coverage of all modules, including:
- Basic functionality
- Edge cases
- Error handling
- Configuration options
When contributing new features or fixing bugs, please ensure that appropriate tests are added or updated to maintain test coverage.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m "Add some amazing feature") - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
ISC
