A high-performance REST API built with Elysia.js, Bun runtime, PostgreSQL, and Sequelize ORM featuring JWT authentication with refresh tokens and MVC architecture.
Created by Mohamed Harbouli
Features • Quick Start • Documentation • API Reference • Contributing
- Features
- Tech Stack
- Architecture
- Project Structure
- Prerequisites
- Quick Start
- Environment Variables
- Database Setup
- Running the Application
- API Reference
- Response Format
- Error Handling
- Database Management
- Testing
- Extending the API
- Best Practices
- Troubleshooting
- Contributing
- License
✨ Modern Stack
- Built with Elysia.js (21x faster than Express.js)
- Powered by Bun runtime for maximum performance
- TypeScript for type safety and better DX
🔐 Advanced Authentication
- JWT access tokens (short-lived, 15 minutes)
- JWT refresh tokens (long-lived, 7 days)
- Secure Cookie-based Authentication (httpOnly, secure, sameSite)
- Secure password hashing with bcrypt
- Token refresh mechanism
- Logout from single/all devices
- Role-based access control (RBAC)
- Protected routes with middleware
🏗️ Clean Architecture
- MVC pattern for separation of concerns
- Modular and scalable structure
- Easy to maintain and extend
🗄️ Database
- PostgreSQL integration
- Sequelize ORM with TypeScript support
- Automatic migrations and schema sync
- User and refresh token management
🔒 Robust
- Input validation with Valibot schema validation
- Comprehensive error handling
- Database connection pooling
- Secure authentication flow
- CORS protection for cross-origin requests
- CSRF protection for cookie-based authentication
- Origin/Referer header validation
📝 Developer Friendly
- Hot reload in development
- Detailed API documentation
- Environment-based configuration
| Technology | Purpose | Version |
|---|---|---|
| Bun | JavaScript Runtime | 1.0+ |
| Elysia.js | Web Framework | Latest |
| TypeScript | Programming Language | 5.0+ |
| Sequelize | ORM | 6.37+ |
| PostgreSQL | Database | 14+ |
| pg | PostgreSQL Driver | 8.16+ |
| jsonwebtoken | JWT Auth | 9.0+ |
| bcrypt | Password Hashing | 6.0+ |
| @elysiajs/cookie | Cookie Management | Latest |
| @elysiajs/cors | CORS Protection | Latest |
| valibot | Schema Validation | Latest |
This project follows the MVC (Model-View-Controller) architectural pattern:
┌─────────────┐
│ Client │
└──────┬──────┘
│
▼
┌─────────────┐
│ Routes │ ◄── Endpoint definitions
└──────┬──────┘
│
▼
┌─────────────┐
│ Controllers │ ◄── Business logic & validation
└──────┬──────┘
│
▼
┌─────────────┐
│ Models │ ◄── Database operations (Sequelize)
└──────┬──────┘
│
▼
┌─────────────┐
│ Database │ ◄── PostgreSQL
└─────────────┘
Benefits:
- Separation of Concerns: Each layer has a single responsibility
- Maintainability: Easy to locate and modify specific functionality
- Testability: Components can be tested independently
- Scalability: Simple to add new features without affecting existing code
elysia-crud-api/
├── src/
│ ├── config/
│ │ └── database.ts # Sequelize database configuration
│ │
│ ├── controllers/
│ │ ├── auth.controller.ts # Authentication logic
│ │ └── item.controller.ts # Item CRUD handlers
│ │
│ ├── models/
│ │ ├── user.model.ts # User model with password hashing
│ │ ├── refreshToken.model.ts # Refresh token management
│ │ └── item.model.ts # Item model
│ │
│ ├── routes/
│ │ ├── auth.routes.ts # Authentication endpoints
│ │ └── item.routes.ts # Item CRUD endpoints
│ │
│ ├── middleware/
│ │ └── auth.middleware.ts # JWT authentication middleware
│ │
│ ├── utils/
│ │ └── jwt.util.ts # JWT token utilities
│ │
│ ├── scripts/
│ │ └── sync-db.ts # Database synchronization utility
│ │
│ └── index.ts # Application entry point
│
├── .env # Environment variables (gitignored)
├── .env.example # Environment template
├── package.json # Dependencies and scripts
├── tsconfig.json # TypeScript configuration
├── bun.lockb # Bun lock file
├── LICENSE # MIT License
└── README.md # This file
src/config/database.ts
- Sequelize instance configuration
- Connection pooling settings
- Environment-based database credentials
src/models/item.model.ts
- Sequelize model definitions
- Database schema
- Data access methods (CRUD operations)
- Type definitions for TypeScript
src/controllers/item.controller.ts
- Request/response handlers
- Business logic
- Input validation
- Error handling
- HTTP status codes
src/routes/item.routes.ts
- API endpoint definitions
- Route-level middleware
- Request schema validation
- Maps routes to controller methods
src/index.ts
- Application initialization
- Database connection
- Route registration
- Server startup
Before you begin, ensure you have the following installed:
-
Bun (v1.0 or higher)
curl -fsSL https://bun.sh/install | bash -
PostgreSQL (v14 or higher)
- macOS:
brew install postgresql@14 - Ubuntu:
sudo apt-get install postgresql-14 - Windows: Download from postgresql.org
- macOS:
-
Git (for cloning the repository)
Get up and running in 5 minutes:
cd /path/to/your/projectbun installCopy the example environment file and update with your database credentials:
cp .env.example .envEdit .env:
DB_HOST=localhost
DB_PORT=5432
DB_NAME=elysia_db
DB_USER=postgres
DB_PASSWORD=your_password_here
NODE_ENV=development
PORT=3000Option A: Using PostgreSQL CLI
createdb elysia_dbOption B: Using psql
psql -U postgres
CREATE DATABASE elysia_db;
\qOption C: Using SQL
CREATE DATABASE elysia_db;bun run db:syncExpected output:
Connecting to database...
✓ Database connection established successfully
Syncing database models...
✓ Database models synced successfully
bun run devExpected output:
✓ Database connection established successfully
Server is running at localhost:3000
curl http://localhost:3000You should see:
{
"message": "Welcome to Elysia CRUD API with MVC Architecture + Authentication",
"version": "1.0.0",
"endpoints": {
"auth": {
"POST /auth/register": "Register new user",
"POST /auth/login": "Login user",
"POST /auth/refresh": "Refresh access token",
"POST /auth/logout": "Logout (revoke refresh token)",
"POST /auth/logout-all": "Logout from all devices",
"GET /auth/profile": "Get current user profile (protected)",
"PUT /auth/profile": "Update user profile (protected)",
"POST /auth/change-password": "Change password (protected)"
},
"items": {
"GET /items": "Get all items",
"GET /items/:id": "Get item by ID",
"POST /items": "Create new item",
"PUT /items/:id": "Update item",
"DELETE /items/:id": "Delete item"
}
}
}Create a .env file in the root directory with the following variables:
# Database Configuration
DB_HOST=localhost # Database host
DB_PORT=5432 # Database port
DB_NAME=elysia_db # Database name
DB_USER=postgres # Database username
DB_PASSWORD=your_password # Database password
# Application Configuration
NODE_ENV=development # Environment (development/production)
PORT=3000 # Server port
FRONTEND_URL=http://localhost:3001 # Frontend URL for CORS
# JWT Configuration
JWT_ACCESS_SECRET=your-super-secret-access-token-key-change-this-in-production
JWT_REFRESH_SECRET=your-super-secret-refresh-token-key-change-this-in-production
JWT_ACCESS_EXPIRY=15m # Access token expiry (15 minutes)
JWT_REFRESH_EXPIRY=7d # Refresh token expiry (7 days)| Variable | Description | Default | Required |
|---|---|---|---|
DB_HOST |
PostgreSQL server host | localhost |
Yes |
DB_PORT |
PostgreSQL server port | 5432 |
Yes |
DB_NAME |
Database name | elysia_db |
Yes |
DB_USER |
Database username | postgres |
Yes |
DB_PASSWORD |
Database password | postgres |
Yes |
NODE_ENV |
Environment mode | development |
No |
PORT |
Server port | 3000 |
No |
JWT_ACCESS_SECRET |
Secret key for access tokens | - | Yes |
JWT_REFRESH_SECRET |
Secret key for refresh tokens | - | Yes |
JWT_ACCESS_EXPIRY |
Access token expiration time | 15m |
No |
JWT_REFRESH_EXPIRY |
Refresh token expiration time | 7d |
No |
FRONTEND_URL |
Frontend URL for CORS | http://localhost:3001 |
No |
createdb elysia_db -U postgres# Connect to PostgreSQL
psql -U postgres
# Create database
CREATE DATABASE elysia_db;
# Verify
\l
# Exit
\qUse tools like:
After creating the database, sync the schema:
bun run db:syncThis command will:
- Connect to the database
- Create the
itemstable if it doesn't exist - Update the table schema to match the model (if changed)
- Add indexes and constraints
Table Schema:
CREATE TABLE items (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);bun run devFeatures:
- Auto-restart on file changes
- Development logging enabled
- Source maps for debugging
bun run src/index.tsFor production, consider:
- Setting
NODE_ENV=production - Using a process manager (PM2, systemd)
- Enabling connection pooling
- Implementing rate limiting
http://localhost:3000
Endpoint: POST /auth/register
Description: Create a new user account
Request Body:
{
"email": "user@example.com",
"password": "password123",
"name": "John Doe"
}Response: 201 Created
{
"success": true,
"message": "User registered successfully",
"data": {
"user": {
"id": 1,
"email": "user@example.com",
"name": "John Doe",
"role": "user",
"isVerified": false,
"createdAt": "2025-11-29T12:00:00.000Z",
"updatedAt": "2025-11-29T12:00:00.000Z"
},
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}Note: accessToken and refreshToken are also set as httpOnly cookies.
Endpoint: POST /auth/login
Description: Authenticate user and get tokens
Request Body:
{
"email": "user@example.com",
"password": "password123"
}Response: 200 OK
{
"success": true,
"message": "Login successful",
"data": {
"user": {
"id": 1,
"email": "user@example.com",
"name": "John Doe",
"role": "user",
"isVerified": false
},
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}Note: accessToken and refreshToken are also set as httpOnly cookies.
Endpoint: POST /auth/refresh
Description: Get a new access token using refresh token
Request Body:
{
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Note: If refreshToken is present in cookies, the body parameter is optional.
Response: 200 OK
{
"success": true,
"message": "Token refreshed successfully",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}Endpoint: POST /auth/logout
Description: Revoke refresh token (logout from current device)
Request Body:
{
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Note: If refreshToken is present in cookies, the body parameter is optional.
Response: 200 OK
{
"success": true,
"message": "Logged out successfully"
}Endpoint: GET /auth/profile
Description: Get current user's profile
Headers:
Authorization: Bearer <access-token>
Note: Or via accessToken cookie.
Response: 200 OK
{
"success": true,
"data": {
"id": 1,
"email": "user@example.com",
"name": "John Doe",
"role": "user",
"isVerified": false,
"createdAt": "2025-11-29T12:00:00.000Z",
"updatedAt": "2025-11-29T12:00:00.000Z"
}
}Endpoint: PUT /auth/profile
Description: Update user profile
Headers:
Authorization: Bearer <access-token>
Request Body:
{
"name": "Jane Doe",
"email": "jane@example.com"
}Response: 200 OK
{
"success": true,
"message": "Profile updated successfully",
"data": {
"id": 1,
"email": "jane@example.com",
"name": "Jane Doe",
"role": "user"
}
}Endpoint: POST /auth/change-password
Description: Change user password
Headers:
Authorization: Bearer <access-token>
Request Body:
{
"currentPassword": "oldpassword123",
"newPassword": "newpassword456"
}Response: 200 OK
{
"success": true,
"message": "Password changed successfully. Please login again with your new password."
}Endpoint: POST /auth/logout-all
Description: Revoke all refresh tokens (logout from all devices)
Headers:
Authorization: Bearer <access-token>
Response: 200 OK
{
"success": true,
"message": "Logged out from 3 device(s) successfully"
}Endpoint: GET /items
Description: Retrieve all items from the database, ordered by creation date (newest first)
Request:
curl http://localhost:3000/itemsResponse: 200 OK
{
"success": true,
"data": [
{
"id": 1,
"name": "Sample Item",
"description": "This is a sample item",
"created_at": "2025-11-29T10:30:00.000Z",
"updated_at": "2025-11-29T10:30:00.000Z"
}
],
"count": 1
}Endpoint: GET /items/:id
Description: Retrieve a specific item by its ID
Parameters:
id(path parameter, required): Item ID (integer)
Request:
curl http://localhost:3000/items/1Response: 200 OK
{
"success": true,
"data": {
"id": 1,
"name": "Sample Item",
"description": "This is a sample item",
"created_at": "2025-11-29T10:30:00.000Z",
"updated_at": "2025-11-29T10:30:00.000Z"
}
}Error Response: 404 Not Found
{
"success": false,
"message": "Item with id 999 not found"
}Endpoint: POST /items
Description: Create a new item
Request Body:
{
"name": "string (required)",
"description": "string (required)"
}Validation:
name: Required, non-empty string, max 255 charactersdescription: Required, non-empty string
Request:
curl -X POST http://localhost:3000/items \
-H "Content-Type: application/json" \
-d '{
"name": "New Item",
"description": "This is a new item description"
}'Response: 201 Created
{
"success": true,
"message": "Item created successfully",
"data": {
"id": 2,
"name": "New Item",
"description": "This is a new item description",
"created_at": "2025-11-29T11:00:00.000Z",
"updated_at": "2025-11-29T11:00:00.000Z"
}
}Error Response: 400 Bad Request
{
"success": false,
"message": "Name and description are required"
}Endpoint: PUT /items/:id
Description: Update an existing item
Parameters:
id(path parameter, required): Item ID (integer)
Request Body:
{
"name": "string (optional)",
"description": "string (optional)"
}Note: At least one field must be provided
Request:
curl -X PUT http://localhost:3000/items/1 \
-H "Content-Type: application/json" \
-d '{
"name": "Updated Item Name"
}'Response: 200 OK
{
"success": true,
"message": "Item updated successfully",
"data": {
"id": 1,
"name": "Updated Item Name",
"description": "Original description",
"created_at": "2025-11-29T10:30:00.000Z",
"updated_at": "2025-11-29T11:15:00.000Z"
}
}Error Response: 404 Not Found
{
"success": false,
"message": "Item with id 999 not found"
}Endpoint: DELETE /items/:id
Description: Delete an item permanently
Parameters:
id(path parameter, required): Item ID (integer)
Request:
curl -X DELETE http://localhost:3000/items/1Response: 200 OK
{
"success": true,
"message": "Item deleted successfully",
"data": {
"id": 1,
"name": "Deleted Item",
"description": "This item was deleted",
"created_at": "2025-11-29T10:30:00.000Z",
"updated_at": "2025-11-29T10:30:00.000Z"
}
}Error Response: 404 Not Found
{
"success": false,
"message": "Item with id 999 not found"
}All API responses follow a consistent structure:
{
"success": true,
"data": {
/* response data */
},
"message": "Optional success message",
"count": "Optional count (for list responses)"
}{
"success": false,
"message": "Error description"
}| Code | Meaning | Usage |
|---|---|---|
| 200 | OK | Successful GET, PUT, DELETE |
| 201 | Created | Successful POST (creation) |
| 400 | Bad Request | Validation error, missing required fields |
| 404 | Not Found | Resource doesn't exist |
| 500 | Internal Server Error | Server-side error |
The API implements comprehensive error handling:
Returned when:
- Required fields are missing
- Data types are incorrect
- Validation rules fail
Example:
{
"success": false,
"message": "Name and description are required"
}Returned when:
- Requested resource doesn't exist
Example:
{
"success": false,
"message": "Item with id 5 not found"
}Returned when:
- Database constraints violated
- Sequelize validation fails
Example:
{
"success": false,
"message": "Validation error: name cannot be empty"
}# Sync database schema
bun run db:sync
# Start development server
bun run devThe db:sync script uses Sequelize's sync() method with alter: true:
await sequelize.sync({ alter: true });What it does:
- Creates tables if they don't exist
- Adds new columns
- Updates column types
- Preserves existing data
sync().
For production environments, use Sequelize migrations:
- Install Sequelize CLI:
bun add -d sequelize-cli- Initialize migrations:
npx sequelize-cli init- Generate migration:
npx sequelize-cli migration:generate --name create-items-table- Run migrations:
npx sequelize-cli db:migrateRun the automated test suite:
bun testTest the complete authentication flow:
# 1. Register a new user
curl -X POST http://localhost:3000/auth/register \
-H "Content-Type: application/json" \
-d '{
"email": "test@example.com",
"password": "password123",
"name": "Test User"
}'
# Response will include accessToken and refreshToken
# Save the accessToken for subsequent requests
# 2. Login (alternative to register)
curl -X POST http://localhost:3000/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "test@example.com",
"password": "password123"
}'
# 3. Get user profile (protected route)
curl http://localhost:3000/auth/profile \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
# 4. Update profile
curl -X PUT http://localhost:3000/auth/profile \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Updated Name"
}'
# 5. Change password
curl -X POST http://localhost:3000/auth/change-password \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"currentPassword": "password123",
"newPassword": "newpassword456"
}'
# 6. Refresh access token
curl -X POST http://localhost:3000/auth/refresh \
-H "Content-Type: application/json" \
-d '{
"refreshToken": "YOUR_REFRESH_TOKEN"
}'
# 7. Logout from current device
curl -X POST http://localhost:3000/auth/logout \
-H "Content-Type: application/json" \
-d '{
"refreshToken": "YOUR_REFRESH_TOKEN"
}'
# 8. Logout from all devices
curl -X POST http://localhost:3000/auth/logout-all \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"Test all item endpoints:
# 1. Create an item
curl -X POST http://localhost:3000/items \
-H "Content-Type: application/json" \
-d '{"name":"Test Item","description":"Testing"}'
# 2. Get all items
curl http://localhost:3000/items
# 3. Get specific item
curl http://localhost:3000/items/1
# 4. Update item
curl -X PUT http://localhost:3000/items/1 \
-H "Content-Type: application/json" \
-d '{"name":"Updated Name"}'
# 5. Delete item
curl -X DELETE http://localhost:3000/items/1-
Import the following collection:
- Base URL:
http://localhost:3000 - Create requests for each endpoint
- Set
Content-Type: application/jsonheader
- Base URL:
-
Test scenarios:
- Happy path (valid data)
- Invalid data (missing fields)
- Non-existent resources (404)
- Edge cases (empty strings, very long text)
Follow these steps to add a new resource:
import { DataTypes, Model, Optional } from "sequelize";
import sequelize from "../config/database";
export interface UserAttributes {
id: number;
email: string;
name: string;
createdAt?: Date;
updatedAt?: Date;
}
interface UserCreationAttributes extends Optional<UserAttributes, "id"> {}
export class User
extends Model<UserAttributes, UserCreationAttributes>
implements UserAttributes
{
public id!: number;
public email!: string;
public name!: string;
public readonly createdAt!: Date;
public readonly updatedAt!: Date;
}
User.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: true,
},
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
sequelize,
tableName: "users",
timestamps: true,
underscored: true,
}
);
class UserModel {
async findAll(): Promise<User[]> {
return await User.findAll();
}
async findById(id: number): Promise<User | null> {
return await User.findByPk(id);
}
async create(email: string, name: string): Promise<User> {
return await User.create({ email, name });
}
async update(
id: number,
data: Partial<UserAttributes>
): Promise<User | null> {
const user = await User.findByPk(id);
if (!user) return null;
await user.update(data);
return user;
}
async delete(id: number): Promise<User | null> {
const user = await User.findByPk(id);
if (!user) return null;
await user.destroy();
return user;
}
}
export const userModel = new UserModel();import { Context } from "elysia";
import { userModel } from "../models/user.model";
export class UserController {
static async getAll() {
const users = await userModel.findAll();
return {
success: true,
data: users,
count: users.length,
};
}
static async getById({ params, set }: Context) {
const id = parseInt(params.id as string);
const user = await userModel.findById(id);
if (!user) {
set.status = 404;
return {
success: false,
message: `User with id ${id} not found`,
};
}
return {
success: true,
data: user,
};
}
static async create({ body, set }: Context) {
const { email, name } = body as { email: string; name: string };
try {
const newUser = await userModel.create(email, name);
set.status = 201;
return {
success: true,
message: "User created successfully",
data: newUser,
};
} catch (error: any) {
set.status = 400;
return {
success: false,
message: error.message || "Error creating user",
};
}
}
// Add update and delete methods similarly...
}import { Elysia, t } from "elysia";
import { UserController } from "../controllers/user.controller";
export const userRoutes = new Elysia({ prefix: "/users" })
.get("/", UserController.getAll)
.get("/:id", UserController.getById)
.post("/", UserController.create, {
body: t.Object({
email: t.String(),
name: t.String(),
}),
});
// Add PUT and DELETE routes...import { userRoutes } from "./routes/user.routes";
const app = new Elysia()
// ... existing routes
.use(itemRoutes)
.use(userRoutes) // Add this line
.listen(3000);Import the new model in src/scripts/sync-db.ts:
import "../models/item.model";
import "../models/user.model"; // Add this linebun run db:sync✅ Do:
- Follow the MVC pattern strictly
- Keep controllers thin (business logic in models)
- Use TypeScript interfaces for type safety
- Implement proper error handling
- Validate input at the route level
❌ Don't:
- Mix database logic in controllers
- Hardcode configuration values
- Skip input validation
- Expose internal errors to clients
✅ Do:
- Use connection pooling
- Implement indexes on frequently queried columns
- Use transactions for multi-step operations
- Close connections properly
❌ Don't:
- Use
sync({ force: true })in production - Store sensitive data in plain text
- Skip database backups
✅ Do:
- Validate and sanitize all inputs
- Use environment variables for secrets
- Implement rate limiting (for production)
- Use HTTPS in production
- Keep dependencies updated
❌ Don't:
- Commit
.envfile - Trust user input
- Expose stack traces in production
- Use default credentials
The API includes comprehensive security measures for cookie-based authentication:
Configured via @elysiajs/cors plugin to allow your frontend to access the API:
cors({
origin: process.env.FRONTEND_URL || "http://localhost:3001",
credentials: true, // Required for cookies
methods: ["GET", "POST", "PUT", "DELETE", "PATCH"],
allowedHeaders: ["Content-Type", "Authorization"],
})Key Configuration:
credentials: true- Essential for sending cookies with cross-origin requestsorigin- Set to your frontend URL (configurable viaFRONTEND_URLenv variable)- In production, set
FRONTEND_URLto your actual frontend domain
Implemented via custom middleware (src/middleware/csrf.middleware.ts) that validates Origin/Referer headers:
How it works:
- Checks the
Originheader matches theHostheader for state-changing requests - Falls back to
Refererheader validation if no Origin is present - Blocks requests from different origins with 403 Forbidden
- Allows API clients (Postman, curl) without Origin/Referer headers
Protected Methods:
- POST
- PUT
- DELETE
- PATCH
Example Error Response:
{
"success": false,
"message": "CSRF validation failed: Origin does not match host"
}Axios:
import axios from 'axios';
const api = axios.create({
baseURL: 'http://localhost:3000',
withCredentials: true, // Important: sends cookies
});
// Login
await api.post('/auth/login', {
email: 'user@example.com',
password: 'password123'
});
// Subsequent requests automatically include cookies
await api.get('/auth/profile');Fetch API:
// Login
await fetch('http://localhost:3000/auth/login', {
method: 'POST',
credentials: 'include', // Important: sends cookies
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'user@example.com',
password: 'password123'
})
});
// Subsequent requests
await fetch('http://localhost:3000/auth/profile', {
credentials: 'include'
});For production, update your .env file:
FRONTEND_URL=https://yourdomain.com
NODE_ENV=productionFor multiple frontend domains:
cors({
origin: [
'https://yourdomain.com',
'https://www.yourdomain.com',
'https://app.yourdomain.com'
],
credentials: true,
// ... other options
})Error:
SequelizeConnectionError: database "elysia_db" does not exist
Solution:
createdb elysia_db -U postgresError:
Error: listen EADDRINUSE: address already in use :::3000
Solution:
# Find process using port 3000
lsof -i :3000
# Kill the process
kill -9 <PID>
# Or use a different port in .env
PORT=3001Error:
SequelizeConnectionError: password authentication failed
Solution:
- Verify credentials in
.env - Check PostgreSQL user exists
- Reset password if needed:
psql -U postgres
ALTER USER postgres PASSWORD 'newpassword';Error:
command not found: bun
Solution:
# Install Bun
curl -fsSL https://bun.sh/install | bash
# Add to PATH (usually automatic)
source ~/.bashrc # or ~/.zshrcError:
Cannot find module './routes/item.routes'
Solution:
# Ensure all files exist
ls -la src/routes/
# Reinstall dependencies
bun install// Add indexes in your model
Item.init(
{
// ... fields
},
{
indexes: [{ fields: ["created_at"] }, { fields: ["name"] }],
}
);Already configured in src/config/database.ts:
pool: {
max: 10, // Maximum connections
min: 0, // Minimum connections
acquire: 30000,
idle: 10000,
}// Instead of loading all fields
const items = await Item.findAll();
// Select specific fields
const items = await Item.findAll({
attributes: ["id", "name"],
});Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Follow existing code style
- Add comments for complex logic
- Update documentation
- Test your changes
- Keep commits atomic
Future enhancements:
- Add authentication (JWT)
- Implement pagination
- Add filtering and sorting
- API rate limiting
- Request logging
- Unit tests
- Integration tests
- Docker support
- CI/CD pipeline
- API documentation (Swagger/OpenAPI)
This project is licensed under the MIT License.
MIT License
Copyright (c) 2025 Mohamed Harbouli
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
If you have questions or need help:
- Open an issue on GitHub
- Check the Elysia documentation
- Check the Sequelize documentation
- Visit Bun documentation
Mohamed Harbouli
- GitHub: @harbouli
- Portfolio: github.com/harbouli
Built with:
- Elysia.js - Web framework
- Bun - JavaScript runtime
- Sequelize - ORM
- PostgreSQL - Database
Made with ❤️ by Mohamed Harbouli