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

jcc-express-starter

v1.3.0

Published

express mvc

Readme

JCC Express MVC Framework Documentation

Welcome to the JCC Express MVC framework documentation. JCC Express MVC is a powerful, expressive web application framework built on Express.js, designed to make web development a creative and enjoyable experience.

📚 Documentation

For complete documentation, visit: https://www.jcc-express.uk/


Introduction

JCC Express MVC is a modern, full-featured web application framework built on Express.js that follows Laravel-style architecture patterns. It is specifically designed and optimized for the Bun runtime, offering a familiar developer experience for those coming from the Laravel ecosystem while leveraging the exceptional performance of Bun.

The framework provides a robust set of tools and features including routing, controllers, middleware, database abstraction with Eloquent ORM, authentication, caching, queues, events, and much more. Whether you're building a simple API or a complex enterprise application, JCC Express MVC has the tools you need.

Why JCC Express MVC?

  • Laravel-Inspired: Familiar patterns and conventions for Laravel developers
  • Bun Optimized: Built specifically for Bun runtime for maximum performance
  • Type-Safe: Full TypeScript support with excellent type inference
  • Eloquent ORM: Beautiful, expressive database interactions
  • Modern Architecture: Service container, dependency injection, and more
  • Developer Experience: Intuitive APIs and comprehensive tooling

Requirements

Before you begin, ensure your machine meets the following requirements:

  • Bun runtime (v1.0.0 or higher) - Required
  • Node.js (v18.0.0 or higher) - For npm package management
  • Database: MySQL 5.7+, PostgreSQL 10+, or SQLite 3.8+
  • Redis (optional) - For caching and queue management

Installation

Prerequisites

Before using this framework, you must have Bun installed on your machine.

  1. Install Bun (if not already installed):
curl -fsSL https://bun.sh/install | bash
  1. Verify Bun installation:
bun --version

Creating a New Project

To create a new JCC Express MVC project, use the official starter template:

bunx jcc-express-starter my-express-app

This command will create a new directory named my-express-app with all the necessary files and directory structure for your application.

Project Setup

After creating your project, navigate to the project directory:

cd my-express-app

Then install the project dependencies:

npm install

Starting the Development Server

Once dependencies are installed, you can start the development server:

bun run dev

Your application will be available at http://localhost:5500 (or the port specified in your .env file).


Configuration

JCC Express MVC uses a centralized configuration system that allows you to manage your application's settings in a clean, organized manner. All configuration files are stored in the app/Config directory, and sensitive values are managed through environment variables.

Environment Configuration

For security and flexibility, JCC Express MVC uses environment files to store sensitive configuration values. Your application's .env file should not be committed to version control, as it may contain API keys, passwords, and other sensitive information.

Environment File Setup

Copy the example environment file to create your own:

cp .env.example .env

Environment Variables

Edit your .env file to configure your application. Here are the essential variables:

# Application
APP_NAME="JCC Express"
APP_ENV=local
APP_KEY=your-secret-key-here
APP_DEBUG=true
APP_URL=http://localhost:5500
PORT=5500

# Database
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=jcc_express
DB_USERNAME=root
DB_PASSWORD=

# Cache
CACHE_DRIVER=memory
CACHE_PREFIX=jcc_express_cache

# Session
SESSION_DRIVER=memory
SESSION_SECRET=your-session-secret

# Redis (optional)
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

Accessing Environment Variables

You can access environment variables in your application using the config helper or by importing the config directly:

import { config } from "@/app/Config";

const appName = config.get("APP_NAME");
const dbConnection = config.get("DB_CONNECTION");

Configuration Files

All configuration files are located in the app/Config/ directory. These files allow you to configure specific aspects of your application.

CORS Configuration

The cors.ts file allows you to configure Cross-Origin Resource Sharing settings:

// app/Config/cors.ts
export const cors = {
  origin: "*", // or specify allowed origins: ["http://localhost:3000"]
  methods: "GET,HEAD,PUT,PATCH,POST,DELETE",
  preflightContinue: false,
  optionsSuccessStatus: 204,
  credentials: true, // Allow cookies
};

Rate Limiting

Configure rate limiting in app/Config/rate-limit.ts:

export const rateLimit = {
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per windowMs
};

Session Configuration

Configure session settings in app/Config/session.ts:

export const session = {
  secret: process.env.SESSION_SECRET || "your-secret-key",
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: process.env.APP_ENV === "production",
    httpOnly: true,
    maxAge: 24 * 60 * 60 * 1000, // 24 hours
  },
};

Global Helpers

JCC Express MVC provides several global helper functions that are available everywhere in your application without needing to import them. These helpers are automatically registered when your application boots.

Available Global Helpers

Application Instance

Access the application instance anywhere in your code:

const userService = app.resolve("UserService");
const config = app.resolve("Config");

Environment Variables

Get environment variable values with optional default:

const port = env("PORT", "5500");
const dbHost = env("DB_HOST");
const appName = env("APP_NAME", "JCC Express");

Event Dispatching

Dispatch events using the emit helper:

import { UserRegistered } from "@/Events/UserRegistered";

// Dispatch an event
await emit(new UserRegistered(user));

Queue Job Dispatching

Dispatch queue jobs using the dispatch helper:

import { ProcessPodcast } from "@/Jobs/ProcessPodcast";

// Dispatch a job immediately
await dispatch(new ProcessPodcast(podcast));

// Jobs with delay are automatically handled
const job = new ProcessPodcast(podcast);
job.delay = 5000; // 5 seconds
await dispatch(job);

String Utilities

Access the Str utility class:

const slug = str().slug("Hello World"); // "hello-world"
const random = str().random(10); // Random string
const camel = str().camel("hello_world"); // "helloWorld"

Password Hashing

Hash and verify passwords:

// Hash a password
const hashedPassword = await bcrypt("plain-text-password");

// Verify a password
const isValid = await verifyHash("plain-text-password", hashedPassword);

JWT Tokens

Sign and verify JWT tokens:

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

// Verify a JWT token
try {
  const payload = jwtVerify(token);
  console.log(payload); // { id: 1, email: "[email protected]" }
} catch (error) {
  // Token is invalid
}

Root Path

Get the root path of your application:

const storagePath = rootPath("storage/app");
const configPath = rootPath("app/Config");
const publicPath = rootPath("public");

Complete Example

Here's an example showing multiple global helpers in use:

import { UserRegistered } from "@/Events/UserRegistered";
import { SendWelcomeEmail } from "@/Jobs/SendWelcomeEmail";

// In a controller or service
async function registerUser(data: any) {
  // Hash password
  const hashedPassword = await bcrypt(data.password);
  
  // Create user
  const user = await User.create({
    ...data,
    password: hashedPassword,
  });
  
  // Dispatch event
  await emit(new UserRegistered(user));
  
  // Dispatch job
  await dispatch(new SendWelcomeEmail(user));
  
  // Generate JWT token
  const token = jwtSign({ id: user.id, email: user.email });
  
  // Get storage path
  const avatarPath = rootPath(`storage/app/avatars/${user.id}`);
  
  return { user, token };
}

Directory Structure

JCC Express MVC follows a convention-over-configuration approach with a familiar, organized directory structure. Understanding this structure will help you know where to place files and how the framework organizes code.

Root Directory Structure

project-root/
├── app/                    # Application core
│   ├── Config/             # Configuration files
│   ├── Events/             # Event classes
│   ├── Http/               # HTTP layer
│   │   ├── Controllers/    # Controller classes
│   │   ├── Middlewares/    # Middleware classes
│   │   ├── Requests/       # Form request classes
│   │   └── kernel.ts       # HTTP kernel (middleware registration)
│   ├── Jobs/               # Queue job classes
│   ├── Listener/           # Event listener classes
│   ├── Models/             # Eloquent model classes
│   ├── Observer/           # Model observer classes
│   ├── Providers/          # Service provider classes
│   ├── Repository/         # Repository classes
│   └── Services/           # Service classes
├── bootstrap/               # Application bootstrapping
│   ├── app.ts              # Application initialization
│   └── providers.ts        # Service provider registration
├── database/                # Database files
│   ├── migrations/         # Database migration files
│   └── seeders/            # Database seeder classes
├── public/                  # Publicly accessible files
│   └── build/              # Compiled assets
├── resources/               # Raw, uncompiled assets
│   ├── views/              # Template files (jsBlade)
│   ├── css/                # CSS files
│   └── js/                 # JavaScript files
├── routes/                  # Route definitions
│   ├── web.ts              # Web routes
│   └── api.ts              # API routes
├── storage/                 # Storage directory
│   ├── app/                # Application files
│   └── sessions/           # Session files
├── tests/                   # Test files
│   ├── Feature/            # Feature tests
│   └── Unit/               # Unit tests
└── server.ts                # Application entry point

The App Directory

The app directory contains the core code of your application. Almost all of your application's classes will be in this directory.

Controllers

Controllers are stored in app/Http/Controllers and handle incoming HTTP requests. They contain the logic for processing requests and returning responses.

Middleware

Middleware classes are stored in app/Http/Middlewares and provide a mechanism for filtering HTTP requests entering your application.

Models

Eloquent models are stored in app/Models. Models allow you to query for data in your database tables and insert new records.

Providers

Service providers are stored in app/Providers and are the central place to bootstrap your application. They bind services into the service container and register event listeners.

The Routes Directory

The routes directory contains all of your route definitions. The framework ships with two route files: web.ts for your web routes and api.ts for your API routes.

The Resources Directory

The resources directory contains your raw, uncompiled assets such as CSS, JavaScript, and view templates.

The Public Directory

The public directory contains the index.php file, which is the entry point for all requests entering your application. This directory also serves as a good place to store assets such as images, fonts, and compiled CSS and JavaScript files.


Lifecycle Overview

Understanding the request lifecycle of JCC Express MVC will help you better understand how the framework works and where to hook into the framework's execution flow.

JCC Express MVC uses Express's native request/response lifecycle, extending Express's Request and Response objects with additional methods through AppRequest and AppResponse interfaces. This means all standard Express middleware and methods work seamlessly with the framework.

Request Lifecycle

Every request to your JCC Express MVC application follows a specific lifecycle path. Understanding this lifecycle is crucial for building effective applications. The framework uses Express's native HTTP handling, so requests and responses are standard Express objects with additional framework methods.

Entry Point

The entry point for all requests to a JCC Express MVC application is the server.ts file. This file is very simple and serves as the starting point for loading the rest of your application:

// server.ts
import { app } from "./bootstrap/app";

app.run();

Application Bootstrap

When a request enters your application, it first goes through the application bootstrap process:

  1. Service Container: The application instance is created and the service container is initialized
  2. Service Providers: All service providers are registered and booted
  3. Configuration: Application configuration is loaded
  4. Routes: Route files are loaded and registered

HTTP Kernel

After bootstrapping, the request is sent to the HTTP kernel (app/Http/kernel.ts). The kernel handles:

  1. Global Middleware: Applies middleware that should run on every request
  2. Route Matching: Matches the request URI to a defined route
  3. Route Middleware: Applies middleware specific to the matched route

Route Execution

Once a route is matched:

  1. Controller Resolution: If the route uses a controller, the controller is instantiated via the service container
  2. Dependency Injection: Constructor and method dependencies are automatically resolved
  3. Method Execution: The controller method or route closure is executed
  4. Response Generation: A response is generated and returned

Response

The response flows back through the middleware stack (in reverse order) and is finally sent to the client.

Service Provider Lifecycle

Service providers are the central place where your application is bootstrapped. They have two lifecycle methods:

  1. Register: Bind services into the service container
  2. Boot: Perform any actions after all providers are registered

Understanding this lifecycle will help you know where to place your code and how to extend the framework's functionality.


Service Container

The JCC Express MVC service container is a powerful tool for managing class dependencies and performing dependency injection. Dependency injection is a method of removing hard-coded class dependencies and replacing them with injected dependencies, making your code more maintainable and testable.

Understanding the Service Container

The service container is the central place where all of your application's services are registered and resolved. It's responsible for automatically resolving class dependencies through constructor injection.

Dependency Injection

The service container automatically resolves dependencies by examining a class's constructor type hints. When the container sees a type-hinted dependency, it will attempt to resolve it from the container.

Basic Dependency Injection

You can type-hint dependencies in your controllers and other classes. The service container will automatically resolve them:

import { Inject } from "jcc-express-mvc";
import { UserService } from "@/Services/UserService";

@Inject()
export class UserController {
  constructor(private userService: UserService) {}

  async index() {
    return this.userService.all();
  }
}

Binding Services

You can bind services into the container in your service providers. It's recommended to use class.name instead of string literals for better maintainability:

import { ServiceProvider } from "jcc-express-mvc";
import { UserService } from "@/Services/UserService";
import { EmailService } from "@/Services/EmailService";

export class AppServiceProvider extends ServiceProvider {
  register(): void {
    // Bind as singleton using class.name (recommended)
    this.app.singleton(UserService.name, () => {
      return new UserService();
    });
    
    // Bind as instance (new instance each time) using class.name
    this.app.bind(EmailService.name, () => {
      return new EmailService();
    });
    
    // You can also bind directly without a factory function
    this.app.singleton(UserService.name, UserService);
    this.app.bind(EmailService.name, EmailService);
  }
}

Why use class.name?

  • More maintainable: If you rename the class, TypeScript will catch errors
  • Less error-prone: No typos in string literals
  • Better IDE support: Autocomplete and refactoring work better
  • Type-safe: TypeScript can verify the class name matches

Resolving Services

You can resolve services from the container using either the class name or class.name:

import { app } from "@/bootstrap/app";
import { UserService } from "@/Services/UserService";

// Using class.name (recommended)
const userService = app.resolve(UserService.name);
// or with type annotation
const userService = app.resolve<UserService>(UserService.name);

// Using string literal (still works, but not recommended)
const userService = app.resolve("UserService");

When to Use the Service Container

You typically don't need to manually interact with the service container. The framework automatically resolves dependencies through constructor injection. However, you may need to interact with the container when:

  • Writing service providers
  • Manually resolving services
  • Binding custom services

Service Providers

Service providers are the central place where all JCC Express MVC application bootstrapping takes place. Your own application, as well as all of the framework's core services, are bootstrapped via service providers.

What are Service Providers?

Service providers are classes that bootstrap your application by binding services into the service container, registering event listeners, and performing other initialization tasks. Think of service providers as the "glue" that holds your application together.

Writing Service Providers

All service providers extend the ServiceProvider class. Most service providers contain a register method and a boot method.

The Register Method

Within the register method, you should only bind things into the service container. You should never attempt to register any event listeners, routes, or any other piece of functionality within the register method.

import { ServiceProvider } from "jcc-express-mvc";
import { UserService } from "@/Services/UserService";
import { EmailService } from "@/Services/EmailService";

export class AppServiceProvider extends ServiceProvider {
  /**
   * Register any application services.
   */
  register(): void {
    // Bind services into the container using class.name (recommended)
    this.app.singleton(UserService.name, () => {
      return new UserService();
    });
    
    // Or bind directly
    this.app.bind(EmailService.name, EmailService);
  }
}

The Boot Method

The boot method is called after all other service providers have been registered, meaning you have access to all other services that have been registered by the framework.

import { ServiceProvider } from "jcc-express-mvc";

export class AppServiceProvider extends ServiceProvider {
  register(): void {
    // ...
  }

  /**
   * Bootstrap any application services.
   */
  boot(): void {
    // Access other services here
    const userService = this.app.resolve("UserService");
    // Perform initialization tasks
  }
}

Registering Providers

All service providers are registered in the bootstrap/providers.ts file:

import { AppServiceProvider } from "@/Providers/AppServiceProvider";
import { EventServiceProvider } from "@/Providers/EventServiceProvider";
import { QueueServiceProvider } from "@/Providers/QueueServiceProvider";

export const providers = [
  AppServiceProvider,
  EventServiceProvider,
  QueueServiceProvider,
  // Add your custom providers here
];

Event Service Providers

For event-related functionality, extend the EventServiceProvider class:

import { EventServiceProvider as ServiceProvider } from "jcc-express-mvc";
import { UserRegistered } from "@/Events/UserRegistered";
import { SendWelcomeEmail } from "@/Listeners/SendWelcomeEmail";

export class EventServiceProvider extends ServiceProvider {
  protected listen: Record<any, Function[]> = {
    UserRegistered: [SendWelcomeEmail],
  };

  protected subscribe: any[] = [];

  register(): void {}
}

Deferred Providers

If your provider only registers bindings in the service container, you may choose to defer its registration until one of its registered bindings is actually needed. Deferring the loading of such a provider will improve the performance of your application, since it is not loaded from the filesystem on every request.


Routing

Basic Routing

The most basic JCC Express routes accept a URI and a closure, providing a very simple and expressive method of defining routes and behavior without complicated routing configuration files:

import { Route } from "jcc-express-mvc/Core";

Route.get("/", (req, res) => {
  return res.json({ message: "Hello World" });
});

// Using Controller Array Syntax
Route.get("/user", [UserController, "index"]);

Available Router Methods

The router allows you to register routes that respond to any HTTP verb:

Route.get(uri, callback);
Route.post(uri, callback);
Route.put(uri, callback);
Route.patch(uri, callback);
Route.delete(uri, callback);

Route Parameters

Required Parameters

Sometimes you will need to capture segments of the URI within your route. For example, you may need to capture a user's ID from the URL. The framework supports both :id (Express-style) and {id} (Laravel-style) syntaxes:

// Using :id syntax (Express-style)
Route.get("/user/:id", (req, res) => {
  return res.json({ id: req.params.id });
});

// Using {id} syntax (Laravel-style) - also works
Route.get("/user/{id}", (req, res) => {
  return res.json({ id: req.params.id });
});

Both syntaxes work identically and can be used interchangeably based on your preference.

Route Model Binding

JCC Express automatically resolves Eloquent models defined in routes or controller actions whose type-hinted variable names match a route segment name.

// Define a route with model binding (both :user and {user} work)
Route.get("/users/:user", [UsersController, "show"]);
// or
Route.get("/users/{user}", [UsersController, "show"]);

// Controller
import { httpContext } from "jcc-express-mvc";
import { Inject, Method } from "jcc-express-mvc";

@Inject()
class UsersController {
  @Method()
  async show(user: User, { res } = httpContext) {
    return res.json(user);
  }
}

Binding by Specific Column

You can specify which column to use for model binding by using the {column$param} syntax:

// Find user by slug instead of ID
Route.get("/users/{slug$user}", [UsersController, "show"]);
// or with Express-style syntax
Route.get("/users/:slug$user", [UsersController, "show"]);

// Controller - the model will be resolved using the 'slug' column
@Inject()
class UsersController {
  @Method()
  async show(user: User, { res } = httpContext) {
    return res.json(user); // User found by slug column
  }
}

Note: The syntax {column$param} or :column$param tells the framework to find the model using the specified column instead of the default primary key.

Route Groups

Route groups allow you to share route attributes, such as middleware, across a large number of routes without needing to define those attributes on each individual route.

Middleware

To assign middleware to all routes within a group, you may use the middleware method before defining the group. Middleware are executed in the order they are listed in the array:

Route.middleware(["auth"]).group(() => {
  Route.get("/", (req, res) => {
    // Uses Auth Middleware
  });

  Route.get("/user/profile", (req, res) => {
    // Uses Auth Middleware
  });
});

Route Prefixes

The prefix method may be used to prefix each route in the group with a given URI. For example, you may want to prefix all route URIs within the group with admin:

Route.prefix("admin").group(() => {
  Route.get("/users", (req, res) => {
    // Matches The "/admin/users" URL
  });
});

Route Controllers

If a group of routes all utilize the same controller, you may use the controller method to define the common controller for all of the routes within the group. Then, when defining the routes, you only need to provide the controller method that they invoke:

Route.controller(OrderController).group(() => {
  // Both :id and {id} syntaxes work
  Route.get("/orders/:id", "show");
  // or
  Route.get("/orders/{id}", "show");
  Route.post("/orders", "store");
});

Controllers

Introduction

Instead of defining all of your request handling logic as closures in your route files, you may wish to organize this behavior using "controller" classes. Controllers can group related request handling logic into a single class.

Basic Controllers

Controllers are stored in the app/Http/Controllers directory.

import { httpContext } from "jcc-express-mvc";
import { User } from "@/Models/User";

export class UserController {
  /**
   * Show the profile for a given user.
   */
  async show({ req, res } = httpContext) {
    const user = await User.find(req.params.id);
    return res.json({ user });
  }
}

Dependency Injection & Method Injection

The framework features an improved controller architecture with method injection and elegant context handling.

Constructor Injection

The JCC Express service container is used to resolve all controllers. As a result, you are able to type-hint any dependencies your controller may need in its constructor.

import { Inject } from "jcc-express-mvc";

@Inject()
class UsersController {
  constructor(private readonly service: UserService) {}
}

Method Injection

In addition to constructor injection, you may also type-hint dependencies on your controller's methods. A common use-case for method injection is injecting the User model into your controller methods.

import { Inject, Method } from "jcc-express-mvc";

@Inject()
class UsersController {
  @Method()
  async show(user: User, { res } = httpContext) {
    return res.json(user);
  }
}

HTTP Context

You can destructure the HTTP context (req, res, next) directly in your method signature:

@Method()
async index({ req } = httpContext) {
    const { query } = req.query;
    // ...
}

Middleware

Introduction

Middleware provide a convenient mechanism for inspecting and filtering HTTP requests entering your application. For example, JCC Express includes a middleware that verifies the user of your application is authenticated. If the user is not authenticated, the middleware will redirect the user to the login screen. However, if the user is authenticated, the middleware will allow the request to proceed further into the application.

Defining Middleware

Middleware can be created inside the app/Http/Middlewares directory. Import Request, Response, and Next from "jcc-express-mvc":

// app/Http/Middlewares/AuthMiddleware.ts
import { Request, Response, Next } from "jcc-express-mvc";

export function AuthMiddleware(req: Request, res: Response, next: Next) {
  if (!req.user) {
    return res.status(401).json({ message: "Unauthorized" });
  }
  next();
}

Note: Always import Request, Response, and Next from "jcc-express-mvc" - these are aliases for AppRequest, AppResponse, and AppNext that provide full type support.

Registering Middleware

Global Middleware

If you want a middleware to run during every HTTP request to your application, list the middleware class in the middleware property of your app/Http/kernel.ts class.

Assigning Middleware to Routes

If you would like to assign middleware to specific routes, you should first assign the middleware a key in your app/Http/kernel.ts file.

// app/Http/kernel.ts
export class Kernel {
  static middlewareAliases = {
    auth: AuthMiddleware,
  };
}

Once defined, you may use the middleware method to assign middleware to a route:

Route.middleware(["auth"]).get("/profile", (req, res) => {
  // ...
});

Requests & Responses

JCC Express MVC extends Express's native Request and Response objects with additional methods through AppRequest and AppResponse interfaces. This provides a clean, fluent interface for working with HTTP requests and responses while maintaining compatibility with all Express methods.

HTTP Requests

JCC Express MVC uses Express's Request object extended with additional methods via the AppRequest interface. All standard Express request methods are available, plus the framework's custom methods.

Accessing The Request

In controllers, you use httpContext to access the request and response objects. TypeScript automatically infers the correct types:

import { httpContext } from "jcc-express-mvc";

class UserController {
  async store({ req, res } = httpContext) {
    const name = req.body.name;
    const email = req.body.email;
    // ...
  }
}

Note: In route closures (like Route.get("/", async (req, res) => {})), TypeScript automatically knows the types - you don't need to import or type them explicitly.

Request Data Methods

Basic Input Access

// Get all input (body + query)
const all = req.body;

// Get specific input value
const name = req.input("name");
const email = req.input("email", "[email protected]"); // With default

// Get query parameters (Express native)
const page = req.query.page;

// Get route parameters (Express native)
const userId = req.params.id;

Input Helper Methods

// Check if input key exists
if (req.has("name")) {
  // ...
}

// Check if input key exists and is not empty
if (req.filled("email")) {
  // ...
}

// Get only specified keys
const data = req.only("name", "email");

// Get all except specified keys
const data = req.except("password", "password_confirmation");

// Merge new data into request
req.merge({ status: "active" });

// Replace all request data
req.replace({ name: "John", email: "[email protected]" });

Authentication & User

// Access authenticated user
const user = req.user;

// Check if user is authenticated
if (req.user) {
  // User is authenticated
}

Flash Messages

// Get all flash messages
const flash = req.flash();

// Get flash messages by type
const successMessages = req.flash("success");
const errorMessages = req.flash("error");

// Set flash message
req.flash("success", "User created successfully!");
req.flash("error", ["Error 1", "Error 2"]); // Multiple messages

Request Validation

// Validate request data
await req.validate({
  name: ["required", "string", "max:255"],
  email: ["required", "email", "unique:users,email"],
  password: ["required", "string", "min:8", "confirmed"],
});

// Get validated data
const validated = await req.validated();

Request Detection Methods

// Check if request is an API request
if (req.isApi()) {
  // Request is to /api/* route
}

// Check if request is AJAX
if (req.ajax()) {
  // Request is XMLHttpRequest
}

// Check if request wants JSON
if (req.wantsJson()) {
  // Accept header includes JSON
}

// Check if request body is JSON
if (req.isJson()) {
  // Content-Type is application/json
}

// Check if request accepts JSON
if (req.acceptsJson()) {
  // Accept header prioritizes JSON
}

// Check if request expects JSON response
if (req.expectsJson()) {
  // Combines isApi, ajax, wantsJson, isJson checks
}

// Check if request is Inertia request
if (req.isInertia()) {
  // Request has X-Inertia header
}

Headers & Cookies

// Get header value
const authHeader = req.header("Authorization");
const userAgent = req.userAgent();

// Get cookie value
const token = req.cookie("auth_token");

// Get bearer token from Authorization header
const token = req.bearerToken();

File Uploads

// Check if file exists
if (req.hasFile("avatar")) {
  // File was uploaded
}

// Get file and store it
const filePath = req.file("avatar").store("avatars");

// Access file directly (Express native)
const file = req.files?.avatar;

Request Information

// Get full URL
const fullUrl = req.fullUrl();

// Check HTTP method
if (req.isMethod("POST")) {
  // ...
}

// Get request ID
const requestId = req.id;

// Access JCC session
const session = req.jccSession;

// Get previous URLs
const previousUrls = req.previsiousUrls;

Complete Request Example

// In route closures, TypeScript automatically infers types - no imports needed
Route.post("/users", async (req, res) => {
  // Validate request
  await req.validate({
    name: ["required", "string"],
    email: ["required", "email"],
  });

  // Get validated data
  const data = await req.validated();

  // Check request type
  if (req.expectsJson()) {
    // Return JSON response
    return res.json({ message: "User created", user: data });
  }

  // Set flash message
  req.flash("success", "User created successfully!");

  // Redirect
  return res.redirect("/users");
});

Note: In route closures, you don't need to import Request or Response - TypeScript automatically knows the types. Only import them if you need to use them in middleware or other contexts.

HTTP Responses

JCC Express MVC uses Express's Response object extended with additional methods via the AppResponse interface. All standard Express response methods are available, plus the framework's custom methods.

JSON Responses

Note: In route closures, TypeScript automatically infers the types for req and res - you don't need to import or type them explicitly.

// JSON response (Express native)
return res.json({
  name: "Abigail",
  state: "CA",
});

// JSON with status code
return res.status(201).json({
  message: "Created",
  data: user,
});

Redirect Responses

// Simple redirect (Express native)
return res.redirect("/home");

// Redirect with status code
return res.redirect(303, "/home");

// Redirect back to previous URL
return res.redirectBack();

// Redirect with flash message
return res.with("User created!", "success").redirect("/users");

View Responses

// Render jsBlade view (Express native res.render)
return res.render("welcome", {
  name: "John",
  title: "Welcome",
});

Inertia Responses

// Render Inertia page
return res.inertia("Users/Index", {
  users: users,
});

// Inertia redirect
return res.inertiaRedirect("/users", "User created!", "success");

File Downloads

// Download file (Express native)
return res.download("/path/to/file.pdf");

// Download with custom filename
return res.download("/path/to/file.pdf", "custom-name.pdf");

Response Headers

// Set header (Express native)
res.set("X-Custom-Header", "value");

// Set multiple headers
res.set({
  "X-Custom-Header": "value",
  "X-Another-Header": "another-value",
});

// Get header
const contentType = res.get("Content-Type");

Status Codes

// Set status code (Express native)
res.status(201);

// Chain with response
return res.status(201).json({ message: "Created" });

// Common status codes
res.status(200); // OK
res.status(201); // Created
res.status(400); // Bad Request
res.status(401); // Unauthorized
res.status(404); // Not Found
res.status(500); // Internal Server Error

Flash Messages with Redirects

// Set flash message and redirect
return res.with("User created successfully!", "success").redirect("/users");

// Different flash types
res.with("Error occurred!", "error").redirect("/users");
res.with("Warning message!", "warning").redirect("/users");
res.with("Info message!", "info").redirect("/users");

Complete Response Example

// In route closures, TypeScript automatically infers types - no imports needed
Route.post("/users", async (req, res) => {
  const user = await User.create(await req.validated());

  if (req.expectsJson()) {
    return res.status(201).json({
      message: "User created",
      user: user,
    });
  }

  return res
    .with("User created successfully!", "success")
    .redirect("/users");
});

Request & Response Lifecycle

JCC Express MVC uses Express's native request/response lifecycle. The framework extends these objects with additional methods while maintaining full compatibility with Express middleware and methods.

Standard Express Methods

All standard Express Request and Response methods are available:

Request Methods:

  • req.body - Request body
  • req.query - Query parameters
  • req.params - Route parameters
  • req.headers - Request headers
  • req.cookies - Request cookies
  • req.get() - Get header
  • req.is() - Check content type
  • And all other Express request methods

Response Methods:

  • res.json() - JSON response
  • res.send() - Send response
  • res.render() - Render view
  • res.redirect() - Redirect
  • res.status() - Set status code
  • res.set() - Set header
  • res.cookie() - Set cookie
  • And all other Express response methods

Framework Extensions

The framework adds the following methods to enhance the Express objects:

AppRequest Extensions:

  • req.validate() - Validate request data
  • req.validated() - Get validated data
  • req.input() - Get input value
  • req.has() - Check if input exists
  • req.filled() - Check if input is filled
  • req.only() - Get only specified keys
  • req.except() - Get all except specified keys
  • req.merge() - Merge new data
  • req.replace() - Replace request data
  • req.flash() - Flash messages
  • req.isApi() - Check if API request
  • req.ajax() - Check if AJAX request
  • req.wantsJson() - Check if wants JSON
  • req.expectsJson() - Check if expects JSON
  • req.isInertia() - Check if Inertia request
  • req.file() - Get uploaded file
  • req.hasFile() - Check if file exists
  • req.store() - Store uploaded file
  • req.bearerToken() - Get bearer token
  • req.userAgent() - Get user agent
  • req.cookie() - Get cookie
  • req.header() - Get header
  • req.isMethod() - Check HTTP method
  • req.fullUrl() - Get full URL

AppResponse Extensions:

  • res.inertia() - Render Inertia page
  • res.inertiaRedirect() - Inertia redirect
  • res.redirectBack() - Redirect to previous URL
  • res.with() - Set flash message and chain

Validation

JCC Express MVC uses the validatorjs package for validation, with custom validation methods registered by the framework. This provides a powerful, flexible validation system that works seamlessly with Express requests.

Introduction

JCC Express MVC provides several different approaches to validate your application's incoming data:

  1. Inline Validation - Using req.validate() directly in controllers
  2. Form Request Classes - Encapsulating validation logic in dedicated request classes

Both approaches use the same validation rules and syntax.

Inline Validation

You can validate request data directly in your controllers using the req.validate() method:

// In route closures, TypeScript automatically infers types - no imports needed
Route.post("/users", async (req, res) => {
  // Validate request data
  await req.validate({
    name: "required|string|max:255",
    email: "required|email|unique:users,email", // Can use table name or model name
    password: "required|string|min:8",
  });

  // Get validated data
  const validated = await req.validated();

  // Create user with validated data
  const user = await User.create(validated);

  return res.json({ user });
});

Validation Rules Syntax

Rules can be specified as:

  • String format: "required|email|min:5"
  • Array format: ["required", "email", "min:5"]
// String format (pipe-separated)
await req.validate({
  name: "required|string|max:255",
  email: "required|email",
});

// Array format
await req.validate({
  name: ["required", "string", "max:255"],
  email: ["required", "email"],
});

Custom Error Messages

You can provide custom error messages:

await req.validate(
  {
    name: "required|string",
    email: "required|email",
  },
  {
    "name.required": "The name field is required.",
    "email.email": "That doesn't look like an email address.",
  }
);

Form Request Validation

For more complex validation scenarios, you may wish to create a "form request". Form requests are custom request classes that encapsulate their own validation and authorization logic.

Creating Form Requests

To create a form request class, use the make:request Artisan command:

bun artisanNode make:request StoreUserRequest

The generated class will be placed in the app/Http/Requests directory.

Writing Form Requests

Form requests are custom request classes that extend FormRequest. Let's look at an example form request:

import { FormRequest } from "jcc-express-mvc/Core/FormRequest";

export class StoreUserRequest extends FormRequest {
  /**
   * Get the validation rules that apply to the request.
   */
  async rules() {
    await this.validate({
      name: "required|string|max:255",
      email: "required|email|unique:users,email", // Can use table name or model name
      password: "required|string|min:8",
    });
  }

  /**
   * Get custom messages for validator errors.
   */
  messages() {
    return {
      "name.required": "The name field is required.",
      "email.unique": "This email is already taken.",
    };
  }
}

Using Form Requests

Now you can type-hint the form request in your controller method. The incoming request data will be automatically validated before the controller method is called:

import { StoreUserRequest } from "@/Http/Requests/StoreUserRequest";
import { httpContext } from "jcc-express-mvc";

class UserController {
  async store(request: StoreUserRequest, { res } = httpContext) {
    // The incoming request has been validated...
    // You can access validated data via request.body or request.validated()
    const validated = await request.validated();
    const { name, email } = validated;
    
    // Create the user...
    const user = await User.create(validated);
    
    return res.json({ user });
  }
}

Available Validation Rules

JCC Express MVC provides a wide variety of validation rules. The framework uses validatorjs as the base validation library, which means all validatorjs rules are available. Additionally, the framework registers custom validation methods for database operations and other framework-specific needs.

All Validatorjs Rules

Since JCC Express MVC uses validatorjs, all standard validatorjs rules are available. Refer to the validatorjs documentation for the complete list of available rules. Common rules include:

  • accepted, active_url, after:date, after_or_equal:date
  • alpha, alpha_dash, alpha_num
  • array, before:date, before_or_equal:date
  • between:min,max, boolean
  • confirmed, date, date_equals:date, date_format:format
  • different:field, digits:value, digits_between:min,max
  • dimensions, distinct, email, exists:table,column
  • file, filled, gt:field, gte:field
  • image, in:value1,value2, in_array:field
  • integer, ip, ipv4, ipv6
  • json, lt:field, lte:field
  • max:value, mimetypes, mimes
  • min:value, not_in:value1,value2
  • not_regex:pattern, nullable, numeric
  • present, regex:pattern, required
  • required_if:field,value, required_unless:field,value
  • required_with:field1,field2, required_with_all:field1,field2
  • required_without:field1,field2, required_without_all:field1,field2
  • same:field, size:value, string
  • timezone, unique:table,column, url, uuid

Framework Custom Rules

The framework registers the following custom validation methods that extend validatorjs:

  • unique:Model,column - Database uniqueness validation
  • nullable - Allows null/empty values (field is optional)
  • sometimes - Only validates the field if it is present in the request
  • file - Validates that a file was uploaded
  • image - Validates that an image file was uploaded

These custom rules are automatically registered and available for use in your validation rules.

Basic Rules

  • required - The field must be present and not empty

String Rules

  • string - The field must be a string
  • min:value - The field must have a minimum length/value
  • max:value - The field must have a maximum length/value
  • alpha - The field must contain only alphabetic characters
  • alphaNum - The field must contain only alphanumeric characters
  • slug - The field must be a valid slug

Numeric Rules

  • numeric or num - The field must be numeric
  • integer or int - The field must be an integer
  • float - The field must be a float
  • decimal - The field must be a decimal number

Email & URL Rules

  • email - The field must be a valid email address
  • url - The field must be a valid URL

Comparison Rules

  • same:field - The field must match another field (e.g., password confirmation)
  • confirmed - The field must have a matching {field}_confirmation field

Database Rules

  • unique:Model,column or unique:table,column - The field must be unique in the database
    • Example: unique:User,email - Checks if email is unique in User model
    • Example: unique:users,email - Checks if email is unique in users table
    • Example: unique:users - Checks if the field value is unique in users table (uses field name as column)
    • You can use either model class name (e.g., User) or table name (e.g., users)
    • If column is not specified, it defaults to the field name being validated

Array & Object Rules

  • array - The field must be an array
  • object - The field must be an object

Boolean Rules

  • boolean or bool - The field must be a boolean value

Special Format Rules

  • json - The field must be valid JSON
  • jwt - The field must be a valid JWT token
  • creditCard - The field must be a valid credit card number
  • phone - The field must be a valid phone number
  • postal:countryCode - The field must be a valid postal code for the given country
  • mongoId - The field must be a valid MongoDB ObjectId

File Rules

  • file - The field must be an uploaded file (validates file presence)
  • image - The field must be an uploaded image file (validates image file presence)

Note: The framework automatically normalizes file and image fields during validation. When using file or image rules, the validator checks for file presence using req.hasFile(field).

Complete Rules Example

await req.validate({
  // Basic
  name: "required|string|max:255",
  
  // Email with uniqueness check (can use table name or model name)
  email: "required|email|unique:users,email",
  
  // Password with confirmation
  password: "required|string|min:8",
  password_confirmation: "required|same:password",
  
  // Numeric
  age: "required|integer|min:18|max:100",
  price: "required|decimal|min:0",
  
  // Optional fields (nullable allows field to be empty)
  bio: "nullable|string|max:1000",
  
  // Conditional validation (only validates if field is present)
  phone: "sometimes|phone",
  
  // Arrays and objects
  tags: "required|array",
  metadata: "nullable|object",
  
  // Special formats
  website: "nullable|url",
  phone_number: "nullable|phone",
  postal_code: "nullable|postal:US",
  
  // File uploads
  document: "required|file",        // Required file upload
  avatar: "nullable|image",        // Optional image upload
  resume: "sometimes|file",        // Only validate if file is provided
});

File Validation Example

Here's a complete example of validating file uploads:

// In route closures, TypeScript automatically infers types - no imports needed
Route.post("/profile", async (req, res) => {
  await req.validate({
    name: "required|string|max:255",
    avatar: "nullable|image",        // Optional profile image
    resume: "required|file",         // Required resume file
  });

  const validated = await req.validated();

  // Handle file uploads
  if (req.hasFile("avatar")) {
    const avatarPath = req.file("avatar").store("avatars");
    // Save avatar path to database
  }

  if (req.hasFile("resume")) {
    const resumePath = req.file("resume").store("resumes");
    // Save resume path to database
  }

  return res.json({ message: "Profile updated" });
});

Custom Validation Messages

You can customize the error messages used by the validator:

// Inline validation
await req.validate(
  {
    email: "required|email",
    password: "required|min:8",
  },
  {
    "email.required": "We need your email address!",
    "email.email": "That doesn't look like an email address.",
    "password.min": "Password must be at least 8 characters.",
  }
);

// In FormRequest
export class StoreUserRequest extends FormRequest {
  async rules() {
    await this.validate({
      email: "required|email",
      password: "required|min:8",
    });
  }

  messages() {
    return {
      "email.required": "We need your email address!",
      "email.email": "That doesn't look like an email address.",
      "password.min": "Password must be at least 8 characters.",
    };
  }
}

Custom Validation Methods

The framework registers custom validation methods that extend validatorjs. These are automatically available and handle framework-specific validation needs.

Custom Rules

The framework provides the following custom validation rules:

unique:Model,column or unique:table,column

Validates that a field value is unique in the database.

await req.validate({
  // Using model class name
  email: "required|email|unique:User,email",
  
  // Using table name
  username: "required|string|unique:users,username",
  
  // Column optional - uses field name as column
  email: "required|email|unique:users", // Checks users.email
  username: "required|string|unique:users", // Checks users.username
});
  • Model/Table: Can be either the model class name (e.g., User) or the table name (e.g., users)
  • column: The database column to check (optional, defaults to the field name being validated)

nullable

Allows the field to be null, undefined, or empty. When combined with other rules, those rules only apply if the field has a value.

await req.validate({
  bio: "nullable|string|max:1000", // bio is optional, but if provided, must be string and max 1000 chars
});

Normalization: If the field is not present in the request, it's normalized to an empty string.

sometimes

Only validates the field if it is present in the request. Useful for partial updates.

await req.validate({
  name: "sometimes|string|max:255", // Only validates if name is provided
});

Normalization: If the field is not present and sometimes is used, the field is skipped during validation.

file

Validates that a file was uploaded for the field.

await req.validate({
  document: "required|file",
});

Normalization: The field is normalized to a boolean (true if file exists, false otherwise) using req.hasFile(field).

image

Validates that an image file was uploaded for the field.

await req.validate({
  avatar: "required|image",
  thumbnail: "nullable|image", // Optional image
});

Normalization: The field is normalized to a boolean (true if image file exists, false otherwise) using req.hasFile(field).

Request Body Normalization

The framework automatically normalizes the request body before validation to handle special cases:

  • sometimes: Fields with this rule are skipped if not present
  • nullable: Fields with this rule are set to empty string if not present
  • file: Fields are normalized to boolean based on req.hasFile(field)
  • image: Fields are normalized to boolean based on req.hasFile(field)

This normalization ensures consistent validation behavior across different request types.

Validation Error Handling

When validation fails, a ValidationException is thrown. The framework automatically:

  1. Flashes old input to the session (accessible via flash.old)
  2. Flashes validation errors to the session (accessible via flash.validation_error)
  3. For web requests: Errors are available in flash messages
  4. For API requests: Returns JSON error response with 422 status code

Accessing Validation Errors in Views

<!-- In jsBlade templates -->
@if(flash.validation_error)
  <div class="errors">
    @foreach(flash.validation_error as field => errors)
      <div class="error">
        <strong>{{ field }}:</strong>
        @if(Array.isArray(errors))
          {{ errors[0] }}
        @else
          {{ errors }}
        @endif
      </div>
    @endforeach
  </div>
@endif

<!-- Access old input -->
<input type="text" name="email" value="{{ flash.old.email }}" />

Handling Validation Errors in Controllers

By default, you don't need try-catch blocks. The framework automatically handles validation errors:

// In route closures, TypeScript automatically infers types - no imports needed
Route.post("/users", async (req, res) => {
  // No try-catch needed - framework handles errors automatically
  await req.validate({
    email: "required|email",
    password: "required|min:8",
  });

  // Validation passed - this code only runs if validation succeeds
  const user = await User.create(await req.validated());
  return res.json({ user });
});

The framework automatically:

  • Returns a 422 JSON response for API requests with validation errors
  • Redirects back with flash errors for web requests

Only use try-catch if you need custom error handling:

import { ValidationException } from "jcc-express-mvc/lib/Error/ValidationException-v2";

Route.post("/users", async (req, res) => {
  try {
    await req.validate({
      email: "required|email",
      password: "required|min:8",
    });

    const user = await User.create(await req.validated());
    return res.json({ user });
  } catch (error) {
    // When try-catch is defined, you must manually handle errors
    if (error instanceof ValidationException) {
      // Custom error handling logic here
      if (req.expectsJson()) {
        return res.status(422).json({
          message: "Validation failed",
          errors: error.errors,
        });
      }
      return res.redirectBack();
    }
    throw error; // Re-throw other errors
  }
});

Note: If you define try-catch in your controller, the framework won't automatically handle validation errors - you must handle them manually.

Error Response Format

When validation fails for API requests, the response format is:

{
  "message": "Validation failed",
  "errors": {
    "email": ["The email field is required."],
    "password": ["The password must be at least 8 characters."]
  }
}

Getting Validated Data

After successful validation, you can access the validated data:

// Using req.validated()
await req.validate({ name: "required|string" });
const validated = await req.validated();

// In FormRequest
const validated = await request.validated();

Database Getting Started

JCC Express MVC makes interacting with databases extremely simple across a variety of database backends using either raw SQL, the fluent query builder, or the Eloquent ORM. Currently, JCC Express MVC supports MySQL, PostgreSQL, and SQLite.

Introduction

Most web applications interact with a database. JCC Express MVC makes connecting to databases and running queries extremely simple across a variety of supported databases using either raw SQL, a fluent query builder, or the Eloquent ORM.

Configuration

The database configuration for your application is located in your .env file and app/Config/ directory. You may define all of your database connections in these configuration files, as well as specify which connection should be used by default.

Environment Configuration

Configure your database connection in the .env file:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=jcc_express
DB_USERNAME=root
DB_PASSWORD=

Supported Databases

JCC Express MVC supports the following database systems:

  • MySQL 5.7+ / MariaDB 10.3+
  • PostgreSQL 10.0+
  • SQLite 3.8.8+

Running Raw SQL Queries

Once you have configured your database connection, you may run queries using the DB facade. The DB facade provides methods for each type of query: select, update, delete, insert, and statement.

import { DB } from "jcc-eloquent/lib/DB";

const users = await DB.select("SELECT * FROM users WHERE active = ?", [1]);

Using Multiple Database Connections

When using multiple connections, you may access each connection via the DB facade's connection method:

const users = await DB.connection("mysql").table("users").get();

Query Builder

The database query builder provides a convenient, fluent interface to creating and running database queries. It can be used to perform most database operations in your application and works on all supported database systems.

Introduction

The JCC Express MVC query builder provides a convenient, fluent interface to creating and running database queries. It can be used to perform most database operations in your application and works on all supported database systems.

Retrieving Results

Retrieving All Rows From A Table

You may use the table method on the DB facade to begin a query. The table method returns a fluent query builder instance for the given table, allowing you to chain more constraints onto the query and then finally retrieve the results of the query using the get method:

import { DB } from "jcc-eloquent/lib/DB";

const users = await DB.table("users").get();

Retrieving A Single Row / Column From A Table

If you just need to retrieve a single row from a database table, you may use the first method:

const user = await DB.table("users").where("name", "John").first();

If you don't even need an entire row, you may extract a single value from a record using the value method:

const email = await DB.table("users").where("name", "John").value("email");

Chunking Results

If you need to work with thousands of database records, consider using the chunk method. This method retrieves a small chunk of results at a time and feeds each chunk into a closure for processing:

await DB.table("users").orderBy("id").chunk(100, (users) => {
  for (const user of users) {
    // Process each chunk of 100 users
  }
});

Select Statements

Specifying A Select Clause

You may not always want to select all columns from a database table. Using the select method, you can specify a custom select clause for the query:

const users = await DB.table("users")
  .select("name", "email as user_email")
  .get();

Where Clauses

Where Clauses

You may use the query builder's where method to add "where" clauses to the query. The most basic call to where requires three arguments: the column, an operator, and the value:

const users = await DB.table("users")
  .where("votes", "=", 100)
  .get();

For convenience, if you want to verify that a column is equal to a given value, you may pass the value directly as the second argument to the where method:

const users = await DB.table("users").where("votes", 100).get();

Or Where Clauses

When chaining together calls to the query builder's where method, the "where" clauses will be joined together using the AND operator. However, you may use the orWhere method to join a clause to the query using the OR operator:

const users = await DB.table("users")
  .where("votes", ">", 100)
  .orWhere("name", "John")
  .get();

Inserts

The query builder also provides an insert method that may be used to insert records into the database table. The insert method accepts an object or array of objects:

await DB.table("users").insert({
  email: "[email protected]",
  votes: 0,
});

// Insert multiple records
await DB.table("users").insert([
  { email: "[email protected]", votes: 0 },
  { email: "[email protected]", votes: 0 },
]);

Auto-Incrementing IDs

If the table has an auto-incrementing id, use the insertGetId method to insert a record and then retrieve the ID:

const id = await DB.table("users").insertGetId({
  email: "[email protected]",
  votes: 0,
});

Updates

In addition to inserting records into the database, the query builder can also update existing records using the update method. The update method, like the insert method, accepts an object containing the columns and values which should be updated:

await DB.table("users")
  .where("id", 1)
  .update({ votes: 1 });

Deletes

The query builder may also be used to delete records from the table via the delete method:

await DB.table("users").where("votes", "<", 100).delete();

Migrations

Migrations are like version control for your database, allowing your team to define and share the application's database schema definition.

Generating Migrations

To create a migration, use the make:migration Artisan command:

bun artisanNode make:migration create_users_table

Migration Structure

A migration class contains two methods: up and down. The up method is used to add new tables, columns, or indexes to your database, while the down method should reverse the operations performed by the up method.

import { Schema } from "jcc-eloquent";

Schema.create("users", (table) => {
  table.id();
  table.string("name");
  table.string("email").unique();
  table.timestamps();
});

Running Migrations

To run your outstanding migrations, execute the migrate Artisan command:

bun artisanNode migrate

Schema Builder Methods

The Schema builder provides a fluent interface for defining database tables and columns. All methods are chainable.

Schema Methods

import { Schema } from "jcc-eloquent";

// Create a table
Schema.create("users", (table) => {
  // Define columns
});

// Modify an existing table
Schema.table("users", (table) => {
  // Add/modify columns
});

// Drop a table
Schema.dropTable("users");

// Drop table (without IF EXISTS)
Schema.drop("users");

// Rename a table
Schema.rename("old_users", "new_users");

// Check if table exists
const exists = await Schema.hasTable("users");

// Check if column exists
const hasColumn = await Schema.hasColumn("users", "email");

// Get column listing
const columns = await Schema.getColumnListing("users");

Column Types

String Types

// String (VARCHAR)
table.string("name");
table.string("name", 100); // With length

// Char
table.char("code", 5);

// Text types
table.text("description");
table.mediumText("content");
table.longText("article");
table.tinyText("excerpt");

Integer Types

// Integer
table.integer("age");

// Big integer
table.bigInteger("user_id");

// Small integer
table.smallInteger("status");

// Medium integer
table.mediumInteger("views");

// Tiny integer
table.tinyInteger("flag", 4);

// Unsigned integers
table.unsignedInteger("count");
table.unsignedBigInteger("id");
table.unsignedSmallInteger("status");
table.unsignedMediumInteger("views");
table.unsignedTinyInteger("flag");

// Auto-incrementing
table.increments("id"); // INT UNSIGNED AUTO_INCREMENT
table.bigIncrements("id"); // BIGINT UNSIGNED AUTO_IN