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

@erwininteractive/mvc

v0.3.0

Published

A lightweight, full-featured MVC framework for Node.js with Express, Prisma, and EJS

Readme

@erwininteractive/mvc

A lightweight, full-featured MVC framework for Node.js 20+ built with TypeScript.

Features

  • Express - Fast, minimal web framework
  • EJS + Alpine.js - Server-side templating with reactive client-side components
  • Optional Database - Add Prisma + PostgreSQL when you need it
  • Optional Redis Sessions - Scalable session management
  • JWT Authentication - Secure token-based auth with bcrypt password hashing
  • CLI Tools - Scaffold apps and generate models/controllers

Quick Start

npx @erwininteractive/mvc init myapp
cd myapp
npm run dev

Visit http://localhost:3000 - your app is running!


Getting Started

Step 1: Create a New Page

Create src/views/about.ejs:

<!doctype html>
<html>
<head>
  <title><%= title %></title>
</head>
<body>
  <h1><%= title %></h1>
  <p>Welcome to my about page!</p>
</body>
</html>

Step 2: Add a Route

Edit src/server.ts:

app.get("/about", (req, res) => {
  res.render("about", { title: "About Us" });
});

Step 3: View Your Page

Visit http://localhost:3000/about


Creating Pages

EJS Templates

Create .ejs files in src/views/. EJS lets you use JavaScript in HTML:

<!-- Output a variable (escaped) -->
<h1><%= title %></h1>

<!-- Output raw HTML -->
<%- htmlContent %>

<!-- JavaScript logic -->
<% if (user) { %>
  <p>Welcome, <%= user.name %>!</p>
<% } %>

<!-- Loop through items -->
<ul>
<% items.forEach(item => { %>
  <li><%= item.name %></li>
<% }); %>
</ul>

<!-- Include another template -->
<%- include('partials/header') %>

Adding Routes

// Simple page
app.get("/contact", (req, res) => {
  res.render("contact", { title: "Contact Us" });
});

// Handle form submission
app.post("/contact", (req, res) => {
  const { name, email, message } = req.body;
  console.log(`Message from ${name}: ${message}`);
  res.redirect("/contact?sent=true");
});

// JSON API endpoint
app.get("/api/users", (req, res) => {
  res.json([{ id: 1, name: "John" }]);
});

Resources

Generate a complete resource (model + controller + views) with one command:

npx erwinmvc generate resource Post

This creates:

  • prisma/schema.prisma - Adds the Post model
  • src/controllers/PostController.ts - Full CRUD controller with form handling
  • src/views/posts/index.ejs - List view
  • src/views/posts/show.ejs - Detail view
  • src/views/posts/create.ejs - Create form
  • src/views/posts/edit.ejs - Edit form

Resource Routes

| Action | HTTP Method | URL | Description | |-----------|-------------|------------------|------------------| | index | GET | /posts | List all | | create | GET | /posts/create | Show create form | | store | POST | /posts | Create new | | show | GET | /posts/:id | Show one | | edit | GET | /posts/:id/edit | Show edit form | | update | PUT | /posts/:id | Update | | destroy | DELETE | /posts/:id | Delete |

Wiring Up Routes

Add to src/server.ts:

import * as PostController from "./controllers/PostController";

app.get("/posts", PostController.index);
app.get("/posts/create", PostController.create);
app.post("/posts", PostController.store);
app.get("/posts/:id", PostController.show);
app.get("/posts/:id/edit", PostController.edit);
app.put("/posts/:id", PostController.update);
app.delete("/posts/:id", PostController.destroy);

Controllers

Generate just a controller (without model/views):

npx erwinmvc generate controller Product

This creates src/controllers/ProductController.ts with CRUD actions:

| Action | HTTP Method | URL | Description | |-----------|-------------|------------------|-------------| | index | GET | /products | List all | | show | GET | /products/:id | Show one | | store | POST | /products | Create | | update | PUT | /products/:id | Update | | destroy | DELETE | /products/:id | Delete |

Using Controllers

import * as ProductController from "./controllers/ProductController";

app.get("/products", ProductController.index);
app.get("/products/:id", ProductController.show);
app.post("/products", ProductController.store);
app.put("/products/:id", ProductController.update);
app.delete("/products/:id", ProductController.destroy);

Database (Optional)

Your app works without a database. Add one when you need it.

Setup

npm run db:setup

Edit .env with your database URL:

DATABASE_URL="postgresql://user:password@localhost:5432/mydb"

Run migrations:

npx prisma migrate dev --name init

Generate Models

npx erwinmvc generate model Post

Edit prisma/schema.prisma to add fields:

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@map("posts")
}

Run migrations again:

npx prisma migrate dev --name add-post-fields

Use in Code

import { getPrismaClient } from "@erwininteractive/mvc";

const prisma = getPrismaClient();

app.get("/posts", async (req, res) => {
  const posts = await prisma.post.findMany();
  res.render("posts/index", { posts });
});

Authentication

import {
  hashPassword,
  verifyPassword,
  signToken,
  verifyToken,
  authenticate,
} from "@erwininteractive/mvc";

// Hash a password
const hash = await hashPassword("secret123");

// Verify a password
const isValid = await verifyPassword("secret123", hash);

// Sign a JWT
const token = signToken({ userId: 1, email: "[email protected]" });

// Protect routes with middleware
app.get("/protected", authenticate, (req, res) => {
  res.json({ user: req.user });
});

CLI Commands

| Command | Description | |---------|-------------| | npx @erwininteractive/mvc init <dir> | Create a new app | | npx erwinmvc generate resource <name> | Generate model + controller + views | | npx erwinmvc generate controller <name> | Generate a CRUD controller | | npx erwinmvc generate model <name> | Generate a database model |

Init Options

| Option | Description | |--------|-------------| | --skip-install | Skip running npm install | | --with-database | Include Prisma database setup | | --with-ci | Include GitHub Actions CI workflow |

Resource Options

| Option | Description | |--------|-------------| | --skip-model | Skip generating Prisma model | | --skip-controller | Skip generating controller | | --skip-views | Skip generating views | | --skip-migrate | Skip running Prisma migrate | | --api-only | Generate API-only controller (no views) |

Other Generate Options

| Option | Description | |--------|-------------| | --skip-migrate | Skip running Prisma migrate (model) | | --no-views | Skip generating EJS views (controller) |


Project Structure

myapp/
├── src/
│   ├── server.ts           # Main app - add routes here
│   ├── views/              # EJS templates
│   ├── controllers/        # Route handlers (optional)
│   └── middleware/         # Express middleware (optional)
├── public/                 # Static files (CSS, JS, images)
├── prisma/                 # Database (after db:setup)
│   └── schema.prisma
├── .env.example
├── .gitignore
├── package.json
└── tsconfig.json

Static Files

Files in public/ are served at the root URL:

public/css/style.css  →  /css/style.css
public/images/logo.png  →  /images/logo.png

App Commands

| Command | Description | |---------|-------------| | npm run dev | Start development server (auto-reload) | | npm run build | Build for production | | npm start | Run production build | | npm run db:setup | Install database dependencies | | npm run db:migrate | Run database migrations |


CI/CD (Optional)

Add GitHub Actions CI to your project for automated testing:

npx @erwininteractive/mvc init myapp --with-ci

Or add CI to an existing project by creating .github/workflows/test.yml:

name: Test

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Build
        run: npm run build

Adding Database Tests

If your app uses a database, add PostgreSQL as a service:

jobs:
  test:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: test
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Run migrations
        run: npx prisma migrate deploy
        env:
          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test

      - name: Run tests
        run: npm test
        env:
          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test

      - name: Build
        run: npm run build

Secrets

For production deployments, add these secrets in your GitHub repository settings:

| Secret | Description | |--------|-------------| | DATABASE_URL | Production database connection string | | REDIS_URL | Production Redis connection string | | JWT_SECRET | Secret key for JWT signing | | SESSION_SECRET | Secret key for session encryption |

Access secrets in your workflow:

env:
  DATABASE_URL: ${{ secrets.DATABASE_URL }}
  JWT_SECRET: ${{ secrets.JWT_SECRET }}

Environment Variables

All optional. Create .env from .env.example:

DATABASE_URL="postgresql://user:pass@localhost:5432/mydb"  # For database
REDIS_URL="redis://localhost:6379"                         # For sessions
JWT_SECRET="your-secret-key"                               # For auth
SESSION_SECRET="your-session-secret"                       # For sessions
PORT=3000                                                  # Server port
NODE_ENV=development                                       # Environment

Learn More

License

MIT