JCC Express

Database

Sequelize

Introduction

JCC Express MVC supports Sequelize as an alternative SQL ORM when DB_ORM=sequelize. Models live in app/Models/ and use Sequelize's class + Model.init(...) pattern — not JCC Eloquent's Model from jcc-express-mvc/Eloquent.

When enabled, the framework registers the connection as:

  • database.connection
  • sequelize

Resolve it with app.resolve("sequelize") or the global app helper during bootstrap.


Enable Sequelize

env
DB_ORM=sequelize
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=your_db
DB_USERNAME=root
DB_PASSWORD=secret

app/Config/database.ts includes the expected sequelize block:

TypeScript
sequelize: {
  database,
  username,
  password,
  options: { dialect, host, port },
  sync: { force: false, alter: true },
  dropTables: false,
}

sync.alter updates tables to match your models in development. Keep sync.force and dropTables disabled in production unless you intentionally reset schema.


Required packages

Bash
npm install sequelize mysql2

For PostgreSQL or SQLite, install the matching dialect driver (pg, better-sqlite3, etc.).


Generate models

With DB_ORM=sequelize, ArtisanNode scaffolds Sequelize models:

Bash
bun artisanNode make:model User
bun artisanNode make:model Post

Files are created in app/Models/. On connect, the framework loads every .ts / .js file in that folder, registers models, and runs static associate on each class that defines it.


Model file structure

Each Sequelize model file should:

  1. Import Model, DataTypes, and Sequelize from the sequelize package.
  2. Resolve the shared connection with app.resolve<Sequelize>("sequelize").
  3. Extend Sequelize's Model class and declare typed attributes.
  4. Define static associate(models) for relationships (optional).
  5. Call ModelName.init(attributes, options) at the bottom of the file.
  6. Export a class whose name matches the filename (e.g. Test.ts exports class Test) so autoloading can register it.

Example (app/Models/Test.ts):

TypeScript
import { Model, DataTypes, Sequelize } from "sequelize";

const sequelize = app.resolve<Sequelize>("sequelize");

export class Test extends Model {
  declare id: number;

  /**
   * Define relationships between models
   * @param models - Record of all loaded model classes
   */
  static associate(models: any) {
    //
  }
}

Test.init(
  {
    id: {
      type: DataTypes.INTEGER,
      primaryKey: true,
      autoIncrement: true,
    },
  },
  {
    sequelize,
    tableName: "tests",
    timestamps: true,
    modelName: "Test",
  }
);

Model.init options

OptionPurpose
sequelizeShared connection from app.resolve("sequelize")
tableNameDatabase table name
modelNameSequelize model name (used for route model binding suffix)
timestampsWhen true, Sequelize manages createdAt / updatedAt

Add columns in the first argument to init using DataTypes (STRING, TEXT, BOOLEAN, DATE, JSON, etc.).


Full example: User model

TypeScript
import { Model, DataTypes, Sequelize } from "sequelize";

const sequelize = app.resolve<Sequelize>("sequelize");

export class User extends Model {
  declare id: number;
  declare name: string;
  declare email: string;
  declare password: string;
  declare createdAt: Date;
  declare updatedAt: Date;

  static associate(models: any) {
    User.hasMany(models.Post, { foreignKey: "userId", as: "posts" });
  }
}

User.init(
  {
    id: {
      type: DataTypes.INTEGER,
      primaryKey: true,
      autoIncrement: true,
    },
    name: {
      type: DataTypes.STRING,
      allowNull: false,
    },
    email: {
      type: DataTypes.STRING,
      allowNull: false,
      unique: true,
    },
    password: {
      type: DataTypes.STRING,
      allowNull: false,
    },
  },
  {
    sequelize,
    tableName: "users",
    timestamps: true,
    modelName: "User",
  }
);

Relationships (associate)

After all models in app/Models/ are loaded, the framework calls static associate(models) on each model. The models argument is a map of loaded classes keyed by filename (User, Post, etc.).

TypeScript
// app/Models/Post.ts
import { Model, DataTypes, Sequelize } from "sequelize";

const sequelize = app.resolve<Sequelize>("sequelize");

export class Post extends Model {
  declare id: number;
  declare title: string;
  declare body: string;
  declare userId: number;

  static associate(models: any) {
    Post.belongsTo(models.User, { foreignKey: "userId", as: "author" });
  }
}

Post.init(
  {
    id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
    title: { type: DataTypes.STRING, allowNull: false },
    body: { type: DataTypes.TEXT },
    userId: { type: DataTypes.INTEGER, allowNull: false },
  },
  {
    sequelize,
    tableName: "posts",
    timestamps: true,
    modelName: "Post",
  }
);

Models are loaded in filename sort order. Define foreign keys on child models and wire associate after both sides exist — parent models that appear earlier in the alphabet are available when associate runs on child models.


Using models in controllers

Import the model class from app/Models/:

TypeScript
import { User } from "@/Models/User";

// List
const users = await User.findAll();

// Find by primary key
const user = await User.findByPk(1);

// Create
const created = await User.create({
  name: "Jane",
  email: "jane@example.com",
  password: "secret",
});

// Update
await user.update({ name: "Jane Doe" });

// Delete
await user.destroy();

// Eager load
const withPosts = await User.findByPk(1, { include: ["posts"] });

Use standard Sequelize query APIs on the model class and instances.


Route model binding

When DB_ORM=sequelize, implicit binding resolves models with findByPk from the route parameter (e.g. {user}User.findByPk(id)). See Routing.

TypeScript
Route.get("/users/{user}", [UsersController, "show"]);
TypeScript
import { Inject, Method } from "jcc-express-mvc/Core/Dependency";
import { User } from "@/Models/User";

@Inject()
export class UsersController {
  @Method({ params: ["user"] })
  async show(user: User) {
    return user;
  }
}

Column binding ({email$user}) uses Sequelize findOne({ where: { email } }).


Access the Sequelize connection

For raw queries or transactions outside model classes:

TypeScript
const sequelize = app.resolve("sequelize");

await sequelize.transaction(async (t) => {
  // ...
});

What happens on connect

When DB_ORM=sequelize, the framework:

  1. Creates the Sequelize instance from app/Config/database.ts.
  2. Authenticates against the database.
  3. Requires every model file in app/Models/ and collects exported classes.
  4. Calls associate(models) on each model that defines it.
  5. Runs sequelize.sync(...) using your sync config.

Summary

  • Set DB_ORM=sequelize and configure database.sequelize in app config.
  • Models live in app/Models/ — use sequelize's Model + Model.init, not JCC Eloquent.
  • Resolve the connection with app.resolve<Sequelize>("sequelize") in each model file.
  • Define relationships in static associate(models).
  • Generate scaffolds with bun artisanNode make:model <Name>.
  • Query with Sequelize APIs (findAll, findByPk, create, etc.).