@exyconn/nodejs
v1.1.9
Published
Production-ready, reusable Node.js backend foundation package with Express, logging, and standardized API responses
Maintainers
Readme
@exyconn/nodejs
A production-ready, reusable Node.js backend foundation package with Express, logging, and standardized API responses.
Features
- 🚀 Instance-based Express App Factory - Create multiple independent server instances
- 📝 Type-safe API Responses - Standardized, consistent response helpers
- 🔧 Config-driven - Type-safe environment loading with Zod validation
- 📊 Centralized Logger - Winston-based logging with multiple transports
- 🛡️ Built-in Security - Helmet, CORS, and rate limiting configured
- 🗄️ MongoDB Support - Easy database connection management
- 📦 Zero Global State - Each instance is completely independent
- ⚡ Modern TypeScript - Full type safety and IntelliSense support
- 🏥 Health Checks - Kubernetes-ready liveness and readiness probes
- 📈 Observability - Request ID, distributed tracing, Prometheus metrics
- ⏰ Background Jobs - Queue-based job processing with retry strategies
Installation
npm install @exyconn/nodejs
# or
yarn add @exyconn/nodejs
# or
pnpm add @exyconn/nodejsPeer Dependencies
npm install expressQuick Start
import { createApp, successRes, HttpStatus } from "@exyconn/nodejs";
// Create an app instance
const app = createApp({
port: 3000,
environment: "development",
appName: "my-api",
});
// Add routes - Direct Express methods (no need for app.app)
app.get("/api/users", (req, res) => {
const users = [
{ id: 1, name: "John Doe" },
{ id: 2, name: "Jane Doe" },
];
successRes(res, users, "Users fetched successfully");
});
app.post("/api/users", (req, res) => {
const newUser = { id: 1, ...req.body };
successRes(res, newUser, "User created successfully");
});
// Start the server
app.start().then(info => {
console.log(`🚀 Server running at ${info.url}`);
});Multiple Instances
Run multiple independent servers in the same process:
import { createApp } from "@exyconn/nodejs";
// API Server
const apiServer = createApp({
port: 3000,
appName: "api-server",
});
// Admin Server
const adminServer = createApp({
port: 4000,
appName: "admin-server",
});
// Start both servers
Promise.all([apiServer.start(), adminServer.start()]).then(([api, admin]) => {
console.log(`API Server: ${api.url}`);
console.log(`Admin Server: ${admin.url}`);
});Configuration
Full Configuration Example
import { createApp } from "@exyconn/nodejs";
const app = createApp({
// Server
port: 3000,
environment: "production",
serverUrl: "https://api.example.com",
appName: "my-api",
apiPrefix: "/api/v1",
// Body Parsing
jsonParser: true,
jsonLimit: "10mb",
urlEncodedParser: true,
urlEncodedLimit: "10mb",
// Trust Proxy (for reverse proxies like nginx)
trustProxy: true,
// CORS
cors: {
developmentOrigins: ["http://localhost:3000", "http://localhost:5173"],
productionOrigins: ["https://app.example.com"],
credentials: true,
},
// Rate Limiting
rateLimit: {
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
},
// MongoDB
mongodb: {
uri: "mongodb://localhost:27017/mydb",
maxPoolSize: 10,
},
// Logger
logger: {
level: "info",
console: true,
file: true,
filePath: "./logs",
},
// Security
security: {
helmet: true,
},
// Graceful Shutdown
gracefulShutdown: true,
shutdownTimeout: 30000,
});Configuration Options
| Option | Type | Default | Description |
| ------------- | ------------------------------------------------------ | ------------------------- | ------------------------------- |
| port | number | 3000 | Server port |
| environment | 'development' \| 'production' \| 'staging' \| 'test' | 'development' | Environment mode |
| serverUrl | string | 'http://localhost:3000' | Deployed server URL |
| appName | string | 'exyconn-app' | Application name (used in logs) |
| apiPrefix | string | '/api' | API prefix for routes |
| cors | CorsConfig \| boolean | true | CORS configuration |
| rateLimit | RateLimitConfig \| boolean | true | Rate limiting configuration |
| mongodb | MongoDBConfig | undefined | MongoDB configuration |
| logger | LoggerConfig | See defaults | Logger configuration |
| security | SecurityConfig | See defaults | Security middleware config |
API Response Helpers
Success Responses
import {
successRes,
successResArr,
createRes,
updateRes,
deleteRes,
paginatedRes,
noContent,
acceptedRes,
} from "@exyconn/nodejs";
// Single item
app.get("/user/:id", (req, res) => {
const user = { id: 1, name: "John" };
successRes(res, user, "User fetched successfully");
});
// Array of items
app.get("/users", (req, res) => {
const users = [{ id: 1 }, { id: 2 }];
successResArr(res, users, "Users listed successfully");
});
// Created resource (201)
app.post("/users", (req, res) => {
const newUser = { id: 1, ...req.body };
createRes(res, newUser, "User created successfully");
});
// Updated resource
app.put("/users/:id", (req, res) => {
const updated = { id: 1, ...req.body };
updateRes(res, updated, "User updated successfully");
});
// Deleted resource
app.delete("/users/:id", (req, res) => {
deleteRes(res, "User deleted successfully");
});
// Paginated response
app.get("/posts", (req, res) => {
const posts = [{ id: 1 }, { id: 2 }];
paginatedRes(res, posts, 1, 10, 100, "Posts listed");
});
// No content (204)
app.patch("/users/:id/read", (req, res) => {
noContent(res);
});Error Responses
import {
badRequest,
unauthorized,
forbidden,
notFound,
conflict,
validationError,
tooManyRequests,
errorRes,
serviceUnavailable,
} from "@exyconn/nodejs";
// Bad Request (400)
badRequest(res, "Invalid input data");
// Unauthorized (401)
unauthorized(res, "Please login to continue");
// Forbidden (403)
forbidden(res, "You do not have permission");
// Not Found (404)
notFound(res, "User not found");
// Conflict (409)
conflict(res, "Email already exists");
// Validation Error (422)
validationError(res, [
{ field: "email", message: "Invalid email format" },
{ field: "password", message: "Password too short" },
]);
// Too Many Requests (429)
tooManyRequests(res, "Rate limit exceeded", 60);
// Internal Server Error (500)
errorRes(res, "Something went wrong");
// Service Unavailable (503)
serviceUnavailable(res, "Service temporarily unavailable", 300);Response Format
All responses follow this consistent format:
{
"success": true,
"statusCode": 200,
"message": "Operation completed successfully",
"data": { ... },
"timestamp": "2024-01-15T10:30:00.000Z",
"path": "/api/users"
}Paginated responses include pagination metadata:
{
"success": true,
"statusCode": 200,
"message": "Data listed successfully",
"data": [...],
"pagination": {
"page": 1,
"limit": 10,
"total": 100,
"totalPages": 10,
"hasNextPage": true,
"hasPrevPage": false,
"nextPage": 2,
"prevPage": null
},
"timestamp": "2024-01-15T10:30:00.000Z"
}Logger
import { createLogger, createConsoleLogger } from "@exyconn/nodejs";
// Create a configured logger
const logger = createLogger(
{
level: "debug",
console: true,
file: true,
filePath: "./logs",
},
"development",
"my-app"
);
// Use the logger
logger.info("Server started", { port: 3000 });
logger.error("Database connection failed", { error: err.message });
logger.debug("Processing request", { userId: 123 });
// Create child logger with context
const userLogger = logger.child({ module: "users" });
userLogger.info("User created", { userId: 1 });MongoDB Connection
import { createApp } from "@exyconn/nodejs";
const app = createApp({
port: 3000,
mongodb: {
uri: "mongodb://localhost:27017/mydb",
maxPoolSize: 10,
},
});
// Connect to database
await app.connectDatabase();
// Check connection status
if (app.isDatabaseConnected()) {
console.log("Database connected!");
}
// Disconnect when done
await app.disconnectDatabase();HTTP Status Codes
import { HttpStatus, getStatusText, isSuccess, isError } from "@exyconn/nodejs";
// Use status codes
res.status(HttpStatus.OK).json({ ... });
res.status(HttpStatus.CREATED).json({ ... });
res.status(HttpStatus.NOT_FOUND).json({ ... });
// Get status text
getStatusText(200); // "OK"
getStatusText(404); // "Not Found"
// Check status categories
isSuccess(200); // true
isError(404); // true
isClientError(400); // true
isServerError(500); // trueUtility Functions
Validation
import {
isValidEmail,
isValidUrl,
isValidObjectId,
isValidUuid,
isValidPhone,
isStrongPassword,
isEmpty,
} from "@exyconn/nodejs";
isValidEmail("[email protected]"); // true
isValidUrl("https://example.com"); // true
isValidObjectId("507f1f77bcf86cd799439011"); // true
isValidUuid("550e8400-e29b-41d4-a716-446655440000"); // true
isValidPhone("+1234567890"); // true
isStrongPassword("MyP@ssw0rd!"); // true
isEmpty(""); // true
isEmpty([]); // true
isEmpty({}); // trueHelpers
import {
sleep,
retry,
generateUuid,
deepClone,
pick,
omit,
chunk,
unique,
capitalize,
camelCase,
formatBytes,
formatDuration,
} from "@exyconn/nodejs";
// Async utilities
await sleep(1000);
const result = await retry(fetchData, { maxAttempts: 3 });
// Object utilities
const id = generateUuid();
const clone = deepClone(obj);
const picked = pick(user, ["id", "name"]);
const omitted = omit(user, ["password"]);
// Array utilities
const chunks = chunk([1, 2, 3, 4, 5], 2); // [[1,2], [3,4], [5]]
const uniqueItems = unique([1, 1, 2, 2, 3]); // [1, 2, 3]
// String utilities
capitalize("hello"); // "Hello"
camelCase("hello_world"); // "helloWorld"
// Formatting
formatBytes(1024); // "1 KB"
formatDuration(3661000); // "1h 1m"Enums & Constants
import {
HttpMethod,
ContentType,
UserRole,
RecordStatus,
SuccessMessages,
ErrorMessages,
PaginationDefaults,
} from "@exyconn/nodejs";
// HTTP Methods
HttpMethod.GET; // "GET"
HttpMethod.POST; // "POST"
// Content Types
ContentType.JSON; // "application/json"
// Roles & Status
UserRole.ADMIN; // "admin"
RecordStatus.ACTIVE; // "active"
// Messages
SuccessMessages.CREATED; // "Resource created successfully"
ErrorMessages.NOT_FOUND; // "Resource not found"
// Defaults
PaginationDefaults.LIMIT; // 10
PaginationDefaults.MAX_LIMIT; // 100Graceful Shutdown
The package handles graceful shutdown automatically when gracefulShutdown: true:
- Listens for
SIGTERM,SIGINT, andSIGUSR2signals - Stops accepting new connections
- Waits for existing requests to complete
- Disconnects database connections
- Exits cleanly
const app = createApp({
port: 3000,
gracefulShutdown: true,
shutdownTimeout: 30000, // Force exit after 30 seconds
});Environment Configuration
Type-safe environment variable loading with Zod validation:
import {
loadEnv,
requireEnv,
getEnv,
parseEnvNumber,
parseEnvBoolean,
parseEnvArray,
baseEnvSchema,
databaseEnvSchema,
createAppEnvSchema,
} from "@exyconn/nodejs";
// Load and validate environment variables
loadEnv(); // Loads from .env file
// Get required environment variables (throws if missing)
const apiKey = requireEnv("API_KEY");
// Get optional environment variables with defaults
const port = getEnv("PORT", "3000");
// Parse typed values
const maxRetries = parseEnvNumber("MAX_RETRIES", 3);
const debugMode = parseEnvBoolean("DEBUG", false);
const allowedHosts = parseEnvArray("ALLOWED_HOSTS"); // comma-separated
// Create validated config with Zod schema
const envSchema = createAppEnvSchema({
API_KEY: z.string().min(1),
FEATURE_FLAG: z.boolean().default(false),
});
const config = envSchema.parse(process.env);Health Checks
Kubernetes-ready health check endpoints:
import {
createHealthHandler,
createLivenessHandler,
createReadinessHandler,
setupHealthRoutes,
createMongoHealthChecker,
createRedisHealthChecker,
createMemoryHealthChecker,
} from "@exyconn/nodejs";
const app = createApp({ port: 3000 });
// Quick setup - adds /health, /health/live, /health/ready
setupHealthRoutes(app.express, {
mongo: createMongoHealthChecker(mongoConnection),
memory: createMemoryHealthChecker(512), // 512MB threshold
});
// Or manual setup
app.get(
"/health",
createHealthHandler({
database: createMongoHealthChecker(mongoConnection),
cache: createRedisHealthChecker(redisClient),
})
);
app.get("/health/live", createLivenessHandler());
app.get(
"/health/ready",
createReadinessHandler({
database: createMongoHealthChecker(mongoConnection),
})
);Observability
Request ID, distributed tracing, and Prometheus metrics:
import {
// Request ID
generateRequestId,
generateShortId,
requestIdMiddleware,
// Tracing
tracingMiddleware,
parseTraceParent,
generateTraceParent,
// Metrics
incrementCounter,
setGauge,
observeHistogram,
startTimer,
getPrometheusMetrics,
requestMetricsMiddleware,
createMetricsHandler,
} from "@exyconn/nodejs";
const app = createApp({ port: 3000 });
// Add request ID to all requests
app.use(requestIdMiddleware());
// Add distributed tracing (W3C Trace Context)
app.use(tracingMiddleware());
// Add request metrics
app.use(requestMetricsMiddleware());
// Expose Prometheus metrics endpoint
app.get("/metrics", createMetricsHandler());
// Custom metrics
incrementCounter("api_calls", { endpoint: "/users" });
setGauge("active_connections", 42);
const stopTimer = startTimer("request_duration");
// ... do work
stopTimer({ endpoint: "/users" }); // Records durationBackground Jobs
Background job processing with queue abstraction:
import {
createQueue,
createScheduler,
JobPriority,
RetryStrategy,
} from "@exyconn/nodejs";
// Create a job queue
const emailQueue = createQueue<{ to: string; subject: string }>({
name: "email-queue",
concurrency: 5,
defaultRetries: 3,
retryStrategy: RetryStrategy.EXPONENTIAL,
});
// Add event handlers
emailQueue.onJobComplete((job, result) => {
console.log(`Email sent to ${job.data.to}`);
});
emailQueue.onJobFailed((job, error) => {
console.error(`Failed to send email: ${error.message}`);
});
// Add jobs to queue
await emailQueue.add({
to: "[email protected]",
subject: "Welcome!",
});
// Add with priority
await emailQueue.add(
{ to: "[email protected]", subject: "VIP Welcome!" },
{ priority: JobPriority.HIGH }
);
// Add delayed job
await emailQueue.add(
{ to: "[email protected]", subject: "Reminder" },
{ delay: 60000 } // 1 minute delay
);
// Create a scheduler for recurring jobs
const scheduler = createScheduler();
scheduler.schedule("cleanup-job", 60000, async () => {
// Runs every 60 seconds
await cleanupOldRecords();
});
scheduler.schedule("daily-report", 86400000, async () => {
// Runs every 24 hours
await generateDailyReport();
});
// Start/stop scheduler
scheduler.start("cleanup-job");
scheduler.stop("cleanup-job");TypeScript Support
The package is written in TypeScript and provides full type definitions:
import type {
AppConfig,
AppInstance,
ApiResponse,
PaginatedResponse,
Logger,
TypedRequest,
TypedResponse,
} from "@exyconn/nodejs";
// Typed request body
interface CreateUserBody {
name: string;
email: string;
}
app.post("/users", (req: TypedRequest<CreateUserBody>, res) => {
const { name, email } = req.body; // Fully typed!
// ...
});License
MIT © Exyconn
Contributing
Contributions are welcome! Please read our Contributing Guide for details.
