Goal: Keep controllers thin (HTTP → validation → call service) and put business logic & DB interaction in services and repositories. This makes code testable, maintainable, and production-ready.
Below I’ll give:
src/
db/
index.ts // sequelize instance, pooling config
models/
user.model.ts
post.model.ts
repositories/
user.repository.ts // raw DB CRUD, single responsibility
services/
user.service.ts // business logic, transactions, orchestration
controllers/
user.controller.ts // thin layer: validation -> service
routes/
user.routes.ts
utils/
errors.ts // custom error classes
logger.ts
app.ts
This prevents controllers from becoming a dumping ground of SQL and business rules.
src/db/index.tsimport { Sequelize } from "sequelize";
import "dotenv/config";
export const sequelize = new Sequelize({
database: process.env.DB_DATABASE!,
username: process.env.DB_USERNAME!,
password: process.env.DB_PASSWORD!,
host: process.env.DB_HOST,
dialect: "postgres",
pool: {
max: 20,
min: 2,
acquire: 30000,
idle: 10000,
},
logging: (sql, ms) => {
// Use your logger instead of console.log in prod
// log sql and execution time (ms)
// logger.debug(sql, { ms });
},
});
export const connectDB = async () => {
try {
await sequelize.authenticate();
// Do NOT call sync({ alter: true }) in prod — use migrations.
console.log("DB connected");
} catch (err) {
console.error("DB connection failed", err);
process.exit(1);
}
};
src/models/user.model.tsimport { DataTypes, Model, Optional } from "sequelize";
import { sequelize } from "../db";
interface UserAttributes {
id: string;
name: string;
email: string;
password: string;
createdAt?: Date;
updatedAt?: Date;
}
interface UserCreationAttrs extends Optional<UserAttributes, "id"> {}
export class User extends Model<UserAttributes, UserCreationAttrs> implements UserAttributes {
public id!: string;
public name!: string;
public email!: string;
public password!: string;
public readonly createdAt!: Date;
public readonly updatedAt!: Date;
}
User.init({
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
name: { type: DataTypes.STRING(100), allowNull: false },
email: { type: DataTypes.STRING(100), allowNull: false, unique: true },
password: { type: DataTypes.STRING(100), allowNull: false },
}, {
sequelize,
tableName: "users",
timestamps: true,
underscored: true,
});