Package Exports
- @nitronjs/framework
Readme
NitronJS
A modern full-stack Node.js framework with React Server Components, Islands Architecture, and built-in tooling. Built on React and Fastify for high performance and developer productivity.
Why NitronJS?
- Zero Config - Start building immediately. No webpack, no babel config, no boilerplate
- Server Components by Default - All components render on the server. Add
"use client"only where needed - Islands Architecture - Client components hydrate independently, keeping your pages fast
- Instant HMR - Changes reflect immediately without losing state
- Full-Stack - Database ORM, authentication, sessions, validation all built-in
Installation
npx @nitronjs/framework my-app
cd my-app
npm run devYour 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, have full access to your database, file system, and never ship JavaScript to the browser.
// resources/views/Site/Home.tsx
import User from '@/app/Models/User.js';
export default async function Home() {
const users = await User.get(); // Direct database access
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.
// resources/views/Components/Counter.tsx
"use client";
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}Use client components inside server components:
// resources/views/Site/Home.tsx (Server Component)
import Counter from '../Components/Counter';
export default function Home() {
return (
<div>
<h1>Welcome</h1>
<Counter /> {/* Hydrates as an island */}
</div>
);
}Layouts
Create Layout.tsx files to wrap pages. Layouts are discovered automatically by walking up the directory tree.
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// resources/views/Layout.tsx
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<meta charSet="UTF-8" />
<title>My App</title>
</head>
<body>
{children}
</body>
</html>
);
}// resources/views/Admin/Layout.tsx
export default function AdminLayout({ children }) {
return (
<div className="admin-wrapper">
<nav>Admin Navigation</nav>
<main>{children}</main>
</div>
);
}Disable layout for a specific page:
export const layout = false;
export default function FullscreenPage() {
return <div>No layout wrapper</div>;
}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');
// Route groups with middleware
Route.prefix('/admin').middleware('auth').group(() => {
Route.get('/dashboard', AdminController.dashboard).name('admin.dashboard');
Route.get('/users', AdminController.users).name('admin.users');
});
// RESTful routes
Route.post('/users', UserController.store);
Route.put('/users/:id', UserController.update);
Route.delete('/users/:id', UserController.destroy);Generate URLs using route names:
<a href={route('user.show', { id: 1 })}>View User</a>Controllers
// app/Controllers/UserController.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;Database
Models
// app/Models/User.js
import { Model } from '@nitronjs/framework';
export default class User extends Model {
static table = 'users';
}Queries
// Find all
const users = await User.get();
// Find by ID
const user = await User.find(1);
// Where clauses
const admins = await User.where('role', '=', 'admin').get();
const active = await User.where('active', true).get();
// Chaining
const results = await User
.where('role', '=', 'admin')
.where('active', true)
.orderBy('created_at', 'desc')
.limit(10)
.get();
// First result
const user = await User.where('email', '=', 'john@example.com').first();
// 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_users_table// database/migrations/2024_01_01_000000_create_users_table.js
import { Schema } from '@nitronjs/framework';
class CreateUsersTable {
static async up() {
await Schema.create('users', (table) => {
table.id();
table.string('name');
table.string('email').unique();
table.string('password');
table.timestamp('created_at');
table.timestamp('updated_at').nullable();
});
}
static async down() {
await Schema.dropIfExists('users');
}
}
export default CreateUsersTable;Run migrations:
npm run migrate # Run pending migrations
npm run migrate:fresh # Drop all tables and re-run
npm run migrate:fresh --seed # Drop, migrate, and seedAuthentication
// In a controller or middleware
class AuthController {
static async login(req, res) {
const { email, password } = req.body;
const success = await req.auth.attempt({ email, password });
if (success) {
return req.redirect('/dashboard');
}
return req.view('Auth/Login', {
error: 'Invalid credentials'
});
}
static async logout(req, res) {
await req.auth.logout();
return res.redirect('/');
}
}
// Check authentication
if (req.auth.check()) {
const user = await req.auth.user();
}Session
// Set value
req.session.set('key', 'value');
// Get value
const value = req.session.get('key');
// Flash messages (one-time)
req.session.flash('success', 'Profile updated!');
const message = req.session.getFlash('success');
// All data
const data = req.session.all();
// Regenerate session ID (after login)
req.session.regenerate();
// CSRF token
const token = req.session.getCsrfToken();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',
age: 'numeric|min:18'
});
if (validation.fails()) {
return res.status(422).send({
errors: validation.errors()
});
}Middleware
Create custom middleware:
// app/Middlewares/CheckAge.js
class CheckAge {
static async handler(req, res) {
if (req.query.age < 18) {
return res.status(403).send('Access denied');
}
// No return = continue to next handler
}
}
export default CheckAge;Register in app/Kernel.js:
export default {
routeMiddlewares: {
'check-age': CheckAge,
'auth': AuthMiddleware,
}
};Apply to routes:
Route.middleware('check-age').get('/restricted', Controller.method);
Route.middleware('auth', 'check-age').get('/admin', AdminController.index);CSS & Tailwind
Put your CSS files in resources/css/. Tailwind CSS is automatically detected and processed.
/* resources/css/app.css */
@import "tailwindcss";CSS files are automatically linked in your views:
// CSS from resources/css/ is available at /storage/css/
<link rel="stylesheet" href="/storage/css/app.css" />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 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 symlinkProject Structure
my-app/
├── app/
│ ├── Controllers/ # Request handlers
│ ├── Middlewares/ # Custom middleware
│ └── Models/ # Database models
├── config/ # Configuration files
│ ├── app.js
│ ├── database.js
│ └── server.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 variablesConfiguration
Access configuration values:
import { Config } from '@nitronjs/framework';
const appName = Config.get('app.name');
const dbHost = Config.get('database.host', 'localhost'); // with defaultEnvironment 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+
Documentation
Full documentation available at nitronjs.dev
License
ISC