npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@exyconn/nodejs

v1.1.9

Published

Production-ready, reusable Node.js backend foundation package with Express, logging, and standardized API responses

Readme

@exyconn/nodejs

A production-ready, reusable Node.js backend foundation package with Express, logging, and standardized API responses.

npm version TypeScript License: MIT

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/nodejs

Peer Dependencies

npm install express

Quick 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); // true

Utility 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({}); // true

Helpers

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; // 100

Graceful Shutdown

The package handles graceful shutdown automatically when gracefulShutdown: true:

  • Listens for SIGTERM, SIGINT, and SIGUSR2 signals
  • 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 duration

Background 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.

Support