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

@nitronjs/framework

v0.3.10

Published

NitronJS is a modern and extensible Node.js MVC framework built on Fastify. It focuses on clean architecture, modular structure, and developer productivity, offering built-in routing, middleware, configuration management, CLI tooling, and native React int

Downloads

1,195

Readme


Quick Start

npx -y @nitronjs/framework my-app
cd my-app
npm run storage:link
npm run dev

Your app will be running at http://localhost:3000


Core Concepts

Server Components (Default)

Every .tsx file in resources/views/ is a Server Component by default. They run on the server and have full access to your database and file system.

// resources/views/Site/Home.tsx
import User from '@models/User';

export default async function Home() {
    const users = await User.get();

    return (
        <div>
            <h1>Users</h1>
            <ul>
                {users.map(user => (
                    <li key={user.id}>{user.name}</li>
                ))}
            </ul>
        </div>
    );
}

Client Components

Add "use client" at the top of a file to make it interactive. These components hydrate on the browser and can use React hooks.

"use client";
import { useState } from 'react';

export default function Counter() {
    const [count, setCount] = useState(0);

    return (
        <button onClick={() => setCount(count + 1)}>
            Count: {count}
        </button>
    );
}

Layouts

Create Layout.tsx files to wrap pages. Layouts are discovered automatically by walking up the directory tree. During SPA navigation, layouts persist and only page content is updated — no full page reloads.

resources/views/
├── Layout.tsx             # Root layout (wraps everything)
├── Site/
│   └── Home.tsx           # Uses root Layout
└── Admin/
    ├── Layout.tsx         # Admin layout (nested inside root)
    └── Dashboard.tsx      # Uses both layouts

Routing

Define routes in routes/web.js:

import { Route } from '@nitronjs/framework';
import HomeController from '../app/Controllers/HomeController.js';
import UserController from '../app/Controllers/UserController.js';

// Basic routes
Route.get('/', HomeController.index).name('home');
Route.get('/about', HomeController.about).name('about');

// Route parameters
Route.get('/users/:id', UserController.show).name('user.show');

// RESTful routes
Route.post('/users', UserController.store).name('user.store');
Route.put('/users/:id', UserController.update).name('user.update');
Route.delete('/users/:id', UserController.destroy).name('user.destroy');

// Route groups with prefix, name prefix, and middleware
Route.prefix('/admin').name('admin.').middleware('auth').group(() => {
    Route.get('/', DashboardController.index).name('dashboard');
    Route.get('/users', AdminUserController.index).name('users');

    // Nested groups
    Route.prefix('/pages').name('pages.').group(() => {
        Route.get('/:id/edit', PagesController.edit).name('edit');
    });
});

// Middleware-only groups
Route.middleware('guest').group(() => {
    Route.get('/login', AuthController.getLogin).name('login');
    Route.post('/login', AuthController.postLogin);
});

URL Generation

The global route() function is available everywhere — controllers, views, and client-side code:

// Basic
route('home')                                        // => "/"

// With parameters
route('user.show', { id: 1 })                        // => "/users/1"

// With query string
route('admin.users', {}, { page: 2, q: 'search' })   // => "/admin/users?page=2&q=search"

// Parameters + query string
route('admin.pages.edit', { id: 5 }, { tab: 'seo' }) // => "/admin/pages/5/edit?tab=seo"

Controllers

// app/Controllers/UserController.js
import User from '../Models/User.js';

class UserController {
    static async index(req, res) {
        const users = await User.get();

        return res.view('User/Index', {
            users
        });
    }

    static async show(req, res) {
        const user = await User.find(req.params.id);

        if (!user) {
            return res.status(404).view('Errors/NotFound');
        }

        return res.view('User/Show', {
            user
        });
    }

    static async store(req, res) {
        const { name, email } = req.body;

        const user = new User();
        user.name = name;
        user.email = email;
        await user.save();

        return res.redirect(route('user.show', { id: user.id }));
    }
}

export default UserController;

Path Aliases

NitronJS provides built-in path aliases for clean imports in .tsx view files:

| Alias | Path | |---|---| | @models/* | app/Models/* | | @controllers/* | app/Controllers/* | | @middlewares/* | app/Middlewares/* | | @views/* | resources/views/* | | @css/* | resources/css/* | | @/* | Project root |


Features

Database

Models

import { Model } from '@nitronjs/framework';

export default class User extends Model {
    static table = 'users';
}

Queries

const users = await User.get();
const user = await User.find(1);
const admins = await User.where('role', '=', 'admin').get();

const results = await User
    .where('active', true)
    .orderBy('created_at', 'desc')
    .limit(10)
    .get();

const user = await User.where('email', '=', '[email protected]').first();

// Aggregates
const total = await User.count();
const maxAge = await User.max('age');

// Create
const user = new User();
user.name = 'John';
user.email = '[email protected]';
await user.save();

// Update
await User.where('id', '=', 1).update({ name: 'Jane' });

// Delete
await User.where('id', '=', 1).delete();

Migrations

npm run make:migration create_posts_table
import { Schema } from '@nitronjs/framework';

class CreatePostsTable {
    static async up() {
        await Schema.create('posts', (table) => {
            table.id();
            table.string('title');
            table.text('body');
            table.string('slug').unique();
            table.boolean('published').default(false);
            table.json('metadata').nullable();
            table.timestamp('created_at');
            table.timestamp('updated_at').nullable();
        });
    }

    static async down() {
        await Schema.dropIfExists('posts');
    }
}

export default CreatePostsTable;
npm run migrate              # Run pending migrations
npm run migrate:fresh        # Drop all tables and re-run
npm run migrate:fresh:seed   # Drop, migrate, and seed
npm run migrate:rollback     # Rollback last batch
npm run migrate:status       # Show migration status

Authentication

class AuthController {
    static async login(req, res) {
        const { email, password } = req.body;
        const success = await req.auth.attempt({ email, password });

        if (success) {
            return res.redirect(route('dashboard'));
        }

        return res.view('Auth/Login', {
            error: 'Invalid credentials'
        });
    }

    static async logout(req, res) {
        await req.auth.logout();

        return res.redirect(route('home'));
    }
}

// Check authentication
if (req.auth.check()) {
    const user = await req.auth.user();
}

// Named guards
req.auth.guard('admin').check();

Sessions

req.session.set('key', 'value');
const value = req.session.get('key');

// Flash messages
req.session.flash('success', 'Profile updated!');
const message = req.session.getFlash('success');

// Regenerate session ID
req.session.regenerate();

Validation

import { Validator } from '@nitronjs/framework';

const validation = Validator.make(req.body, {
    name: 'required|string|min:2|max:100',
    email: 'required|email',
    password: 'required|string|min:8|confirmed',
    age: 'numeric|min:18',
    avatar: 'file|mimes:png,jpg|max:2097152'
});

if (validation.fails()) {
    return res.status(422).send({
        errors: validation.errors()
    });
}

Middleware

// app/Middlewares/CheckAge.js
class CheckAge {
    static async handler(req, res) {
        if (req.query.age < 18) {
            return res.status(403).send('Access denied');
        }
    }
}

export default CheckAge;

Register in app/Kernel.js:

export default {
    routeMiddlewares: {
        'check-age': CheckAge,
        'auth': AuthMiddleware
    }
};

Mail

import { Mail } from '@nitronjs/framework';

await Mail.from('[email protected]')
    .to('[email protected]')
    .subject('Welcome!')
    .html('<h1>Hello</h1>')
    .attachment({ filename: 'file.pdf', path: '/path/to/file.pdf' })
    .send();

// Using a template
await Mail.to('[email protected]')
    .view('emails/welcome', { name: 'Alice' })
    .send();

Storage

import { Storage } from '@nitronjs/framework';

await Storage.put(file, 'upload_files', 'image.jpg');
const buffer = await Storage.get('upload_files/image.jpg');
await Storage.delete('upload_files/old.jpg');
await Storage.move('upload_files/a.jpg', 'upload_files/b.jpg');
Storage.exists('upload_files/image.jpg');
Storage.url('upload_files/image.jpg');  // => "/storage/upload_files/image.jpg"

Encryption

import { AES } from '@nitronjs/framework';

const token = AES.encrypt({ userId: 1, expires: '2025-12-31' });
const data = AES.decrypt(token);  // Returns false on tamper

Hashing

import { Hash } from '@nitronjs/framework';

const hashed = await Hash.make('password123');
const valid = await Hash.check('password123', hashed);

Logging

import { Log } from '@nitronjs/framework';

Log.info('User registered', { userId: 1 });
Log.error('Payment failed', { orderId: 123 });
Log.debug('Query executed', { sql: '...' });

DateTime

import { DateTime } from '@nitronjs/framework';

DateTime.toSQL();                    // "2025-01-15 10:30:00"
DateTime.getDate(timestamp, 'Y-m-d H:i:s');
DateTime.addDays(7);
DateTime.subHours(2);

Faker

Built-in fake data generator for seeders and testing:

import { Faker } from '@nitronjs/framework';

Faker.fullName();       // "John Smith"
Faker.email();          // "[email protected]"
Faker.sentence();       // "Lorem ipsum dolor sit amet."
Faker.int(1, 100);      // 42
Faker.boolean();        // true
Faker.uuid();           // "550e8400-e29b-41d4-a716-446655440000"
Faker.creditCard();     // Luhn-valid card number
Faker.hexColor();       // "#a3f29c"
Faker.oneOf(['a', 'b', 'c']);

String Utilities

import { Str } from '@nitronjs/framework';

Str.slug('Hello World');       // "hello-world"
Str.camel('user_name');        // "userName"
Str.pascal('user_name');       // "UserName"
Str.snake('userName');         // "user_name"
Str.random(32);                // Random string
Str.limit('Long text...', 10); // "Long text..."

CLI Commands

# Development
npm run dev              # Start dev server with HMR
npm run build            # Build for production
npm run start            # Start production server

# Database
npm run migrate          # Run migrations
npm run migrate:fresh    # Fresh migration
npm run migrate:fresh:seed # Fresh migration + seed
npm run migrate:rollback # Rollback last batch
npm run migrate:status   # Show migration status
npm run seed             # Run seeders

# Code Generation
npm run make:controller <name>
npm run make:model <name>
npm run make:middleware <name>
npm run make:migration <name>
npm run make:seeder <name>

# Utilities
npm run storage:link     # Create storage symlink

Project Structure

my-app/
├── app/
│   ├── Controllers/      # Request handlers
│   ├── Middlewares/       # Custom middleware
│   └── Models/            # Database models
├── config/                # Configuration files
│   ├── app.js
│   ├── auth.js
│   ├── database.js
│   ├── hash.js
│   ├── server.js
│   └── session.js
├── database/
│   ├── migrations/        # Database migrations
│   └── seeders/           # Database seeders
├── public/                # Static assets
├── resources/
│   ├── css/               # Stylesheets
│   └── views/             # React components (TSX)
├── routes/
│   └── web.js             # Route definitions
├── storage/               # File storage
└── .env                   # Environment variables

CSS & Tailwind

Put your CSS files in resources/css/. Tailwind CSS v4 is automatically detected and processed.

/* resources/css/global.css */
@import "tailwindcss";

Import in your .tsx files using the @css alias:

import "@css/global.css";

Configuration

Access configuration values:

import { Config } from '@nitronjs/framework';

const appName = Config.get('app.name');
const dbHost = Config.get('database.host', 'localhost');

Environment variables in .env:

APP_NAME=MyApp
APP_DEV=true
APP_PORT=3000

DATABASE_HOST=127.0.0.1
DATABASE_PORT=3306
DATABASE_NAME=myapp
DATABASE_USERNAME=root
DATABASE_PASSWORD=

Requirements

  • Node.js 18+
  • MySQL 5.7+ or MariaDB 10.3+

License

ISC