Getting Started
Pearl is a TypeScript framework for Node.js that ships everything you need in one install: routing, JWT auth, Drizzle ORM, request validation, BullMQ queues, typed events, and mail — all pre-integrated, zero glue required.
Requirements
- Node.js v20 or higher
- TypeScript v5.4 or higher
- PostgreSQL, MySQL, or SQLite (for database features)
- Redis (only needed for queue features)
Scaffold a new project
The fastest way to get started. One command creates a fully structured project, installs dependencies, and generates a .env file for you.
npx @pearl-framework/cli new my-api
cd my-api
npm run devYour server is now running at http://localhost:3000. That's it.
Manual install
Prefer to wire things up yourself? Install the meta-package — it includes every Pearl sub-package in one go:
npm install @pearl-framework/pearl drizzle-orm zod dotenvProject structure
After scaffolding, your project looks like this:
my-api/
├── src/
│ ├── controllers/ # HTTP route handlers
│ ├── events/ # Domain events
│ ├── jobs/ # Background jobs
│ ├── listeners/ # Event listeners
│ ├── mail/ # Mailable email classes
│ ├── middleware/ # Custom middleware
│ ├── schema/ # Drizzle table definitions
│ ├── providers/
│ │ └── AppServiceProvider.ts
│ ├── requests/ # FormRequest validation
│ └── server.ts # Entry point
├── database/
│ └── migrations/
├── .env # Auto-created and auto-loaded on boot
├── .env.example
├── package.json
└── tsconfig.jsonThe entry point
src/server.ts is generated for you by the scaffold. Two important things to understand before you change anything:
import 'dotenv/config'is included at the top of the generatedserver.ts— this loads your.envbefore anything else runs.root: import.meta.dirnametells Pearl where your project root is so it can find config files. Don't remove it.
import 'dotenv/config'
import { Application, HttpKernel, Router } from '@pearl-framework/pearl'
import { AppServiceProvider } from './providers/AppServiceProvider.js'
const app = new Application({ root: import.meta.dirname })
app.register(AppServiceProvider)
await app.boot() // all providers booted
const router = new Router()
router.get('/', (ctx) =>
ctx.response.json({ message: 'Welcome to Pearl 🦪' })
)
await new HttpKernel()
.useRouter(router)
.listen(Number(process.env.PORT ?? 3000))
console.log('🦪 Pearl running on http://localhost:3000')Service providers
A service provider is where you register services into Pearl's IoC container — your database, auth guards, queue manager, and anything else your app needs. The scaffold generates one at src/providers/AppServiceProvider.ts.
Each provider has two lifecycle methods:
register()— bind services into the container. Must be synchronous. Do not callmake()here — other providers may not have registered yet.boot()— called after all providers are registered and.envis loaded. Safe for async work like opening DB connections.
import { ServiceProvider, DatabaseManager } from '@pearl-framework/pearl'
import { DrizzleAdapter } from '@pearl-framework/database'
export class AppServiceProvider extends ServiceProvider {
register(): void {
// Bind DatabaseManager as a singleton — constructed once, reused everywhere
this.container.singleton(DatabaseManager, () =>
new DatabaseManager(new DrizzleAdapter({
driver: 'postgres',
host: process.env.DB_HOST ?? 'localhost',
port: Number(process.env.DB_PORT ?? 5432),
user: process.env.DB_USER ?? 'postgres',
password: process.env.DB_PASSWORD ?? '',
database: process.env.DB_NAME ?? 'my_api',
}))
)
}
override async boot(): Promise<void> {
// .env is loaded by this point — safe to read env vars and connect
await this.container.make(DatabaseManager).connect()
}
}Environment variables
The scaffold creates a .env from .env.example. Edit it before starting the server:
APP_NAME=my-api
PORT=3000
# Database
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=secret
DB_NAME=my_api
# Redis — only needed if you use queues
REDIS_HOST=localhost
REDIS_PORT=6379
# Generate with: openssl rand -base64 32
JWT_SECRET=change-this-in-productionNext steps
- Define your first routes
- Organise handlers into controllers
- Set up your database schema with Drizzle ORM
- Add JWT authentication
- Validate incoming requests with FormRequest
- Offload slow work to background queues