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

saas-automation-framework

v1.0.0

Published

Production-ready SaaS starter framework for Node.js, PostgreSQL, Docker, and CI/CD.

Readme

SAAS Automation Framework

A production-ready SaaS starter framework built on Node.js + PostgreSQL. Clone it, add your modules, and ship — user accounts, auth, roles, rate limiting, migrations, and CI/CD are already wired up.

Stack: Express.js + PostgreSQL + JWT + Zod + Docker


How It Works

Request Lifecycle

Every HTTP request flows through this chain:

Request → Helmet → CORS → Body parser → Rate limiter
       → Router → [authMiddleware] → [requireRole] → [validate]
       → Controller → Service → Repository → PostgreSQL
       → Response  (or → errorHandler if anything throws)

The Module Pattern

To add a new feature (e.g. "posts"), create 5 files in src/modules/posts/:

| File | Responsibility | |------|---------------| | posts.schema.js | Zod validation shapes for request bodies | | posts.repository.js | Extends BaseRepository — SQL queries | | posts.service.js | Business logic (auth checks, transforms) | | posts.controller.js | HTTP handlers (req/res), calls service | | posts.routes.js | Express router, applies middleware |

Then register the router in src/routes/index.js.

Key Building Blocks

  • BaseRepository — Generic CRUD (findById, findMany, create, update, delete) for any table. Extend it, don't modify it.
  • HttpError subclasses — Throw new NotFoundError() anywhere; the global error handler catches it and sends the right HTTP status.
  • asyncHandler — Wraps every route handler so rejected promises get forwarded to the error handler automatically.
  • validate(schema) — Middleware factory: pass a Zod schema, it validates req.body and returns 422 with field errors on failure.
  • authMiddleware — Verifies JWT, injects req.user for downstream handlers.
  • requireRole('admin') — Guards a route to specific roles; must come after authMiddleware.

Stack

  • Runtime: Node.js 22 (ESM)
  • Framework: Express.js
  • Database: PostgreSQL 15 (raw SQL via pg)
  • Validation: Zod
  • Auth: JWT (jsonwebtoken) + bcrypt
  • Logging: Pino (structured)
  • Rate Limiting: express-rate-limit
  • Security: Helmet (HTTP headers)
  • Testing: Jest + Supertest
  • Containers: Docker + Docker Compose
  • CI/CD: GitHub Actions → GHCR → deploy webhook

Project Structure

├── src/
│   ├── index.js              # Entry point, graceful shutdown
│   ├── app.js                # Express app factory + middleware stack
│   ├── config/
│   │   ├── database.js       # pg Pool, query helper
│   │   └── env.js            # Zod env validation (fails fast on startup)
│   ├── lib/
│   │   ├── BaseRepository.js # Generic CRUD base class
│   │   ├── HttpError.js      # Typed HTTP error hierarchy
│   │   ├── asyncHandler.js   # Wraps async route handlers
│   │   └── validate.js       # Zod request body validation middleware
│   ├── middleware/
│   │   ├── auth.js           # JWT Bearer auth → req.user
│   │   ├── errorHandler.js   # 404 + global error handler
│   │   ├── rateLimiter.js    # Global + auth-specific rate limits
│   │   └── requireRole.js    # Role-based access control
│   ├── modules/
│   │   ├── auth/             # register, login, /me
│   │   └── users/            # CRUD with owner/admin authorization
│   ├── routes/
│   │   ├── index.js          # Route aggregator
│   │   └── health.js         # GET /api/health
│   └── utils/
│       └── logger.js         # Pino logger
├── scripts/
│   ├── migrate.js            # SQL migration runner
│   └── migrations/
│       └── 001_initial.sql   # users table (UUID, role, timestamps)
├── tests/
│   ├── setup.js              # Loads .env before test suite
│   ├── health.test.js        # Health + 404 sanity checks
│   └── auth.test.js          # Full auth flow + edge cases
├── Dockerfile
├── docker-compose.yml        # app + postgres + redis
└── .github/workflows/ci.yml

Quick Start

# 1. Copy env
cp .env.example .env

# 2. Start services
docker compose up --build

# 3. Run migrations (first time only)
docker compose exec app npm run migrate

App will be available at http://localhost:3000/api/health.

Development (local, no Docker)

npm install
cp .env.example .env   # edit DATABASE_URL to point at your local Postgres
npm run migrate
npm run dev

Authentication

Flow: Register → Login → Use Protected Routes

POST /api/auth/register   → 201 + { user, token }
POST /api/auth/login      → 200 + { user, token }
GET  /api/auth/me         → 200 + { user }          (requires Bearer token)

Both /register and /login are protected by the auth rate limiter (10 req/15 min).

Token Usage

Authorization: Bearer <token>

JWT payload: { userId, email, role }. Default expiry: 7d.


Users API

All routes require a valid JWT.

GET    /api/users/       → list all users      [admin only]
GET    /api/users/:id    → get user by ID      (own record or admin)
PATCH  /api/users/:id    → update email        (own record or admin)
DELETE /api/users/:id    → delete user         [admin only]

Tests

Tests use Jest + Supertest (real HTTP requests against the live app + database):

  • tests/health.test.js — 200 on health, 404 on unknown routes
  • tests/auth.test.js — register/login/me happy paths + edge cases (duplicate email, bad password, tampered token)

Each test creates a unique email (test-${Date.now()}@example.com) and cleans up with afterAll.

npm test

Database Migrations

Migration files live in scripts/migrations/ and are named NNN_description.sql. The runner tracks applied migrations in a _migrations table, wraps each in a transaction, and rolls back on error.

npm run migrate
# or in Docker:
docker compose exec app npm run migrate

CI/CD

On every push to main:

  1. Test — Spins up a Postgres service container, runs migrations and tests.
  2. Build — Builds and pushes a Docker image to GitHub Container Registry tagged :latest and :<sha>.
  3. Deploy — Triggers your deployment webhook (configure DEPLOY_WEBHOOK_URL in GitHub secrets).

Environment Variables

See .env.example for all variables. Two are required at startup (validated by Zod — server won't start without them):

| Variable | Required | Default | Notes | |----------|----------|---------|-------| | DATABASE_URL | ✅ | — | PostgreSQL connection string | | JWT_SECRET | ✅ | — | Must be ≥ 32 characters | | NODE_ENV | — | development | development | test | production | | PORT | — | 3000 | | | JWT_EXPIRES_IN | — | 7d | | | ALLOWED_ORIGINS | — | * | CORS origins | | LOG_LEVEL | — | info | fataltrace |


Security

  • Helmet (HTTP security headers)
  • CORS (configurable via ALLOWED_ORIGINS)
  • Rate limiting (global 100 req/15 min, auth routes 10 req/15 min)
  • JWT verification on protected routes
  • Bcrypt password hashing (12 rounds) + constant-time comparison
  • Parameterized queries (SQL injection protection)
  • Role-based access control (user / admin)
  • Non-root Docker user
  • Environment validated at startup via Zod

Adding a Feature

  1. Add a migration in scripts/migrations/NNN_name.sql
  2. Create src/modules/your-feature/ with the 5-file module pattern
  3. Mount the router in src/routes/index.js
  4. Write tests in tests/your-feature.test.js

Roadmap

The core is intentionally lean. These are the natural next modules for a full SaaS product:

| Module | What it covers | |--------|----------------| | Billing | Stripe webhook handling, subscription status, plan tiers stored on the user record | | Email | Transactional emails — verification on register, password reset flow | | API Keys | Alternative to JWT for programmatic/server-to-server access | | Organizations | Multi-tenancy — users belong to an org, resources are scoped to it | | Audit Log | Append-only log of user actions for compliance and debugging |