JSPM

  • Created
  • Published
  • Downloads 84
  • Score
    100M100P100Q101076F
  • License ISC

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 integration for scalable full-stack applications.

Package Exports

  • @nitronjs/framework

Readme

NitronJS

A full-stack MVC framework built on Fastify and React Server Components.
Build production-ready Node.js applications with a built-in ORM, authentication, mail, validation, and instant HMR.

npm version license node version

NitronJS is currently under active development. APIs may change before the stable release.


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', '=', 'john@example.com').first();

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

// Create
const user = new User();
user.name = 'John';
user.email = 'john@example.com';
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('noreply@app.com')
    .to('user@email.com')
    .subject('Welcome!')
    .html('<h1>Hello</h1>')
    .attachment({ filename: 'file.pdf', path: '/path/to/file.pdf' })
    .send();

// Using a template
await Mail.to('user@email.com')
    .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();          // "john@example.com"
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