v1.3.0  ·  11 packages  ·  now on npm

The TypeScript backend
framework.

Routing, auth, validation, queues, mail — already integrated, secure by default.

$npm install @pearl-framework/pearl
src/server.ts
import { Application, HttpKernel, Router,
         AuthManager, Authenticate } from '@pearl-framework/pearl'
import { AppServiceProvider } from './providers/AppServiceProvider.js'

const app = new Application({ root: import.meta.dirname })
app.register(AppServiceProvider)
await app.boot()  // loads .env + boots providers

const auth   = app.container.make(AuthManager)
const router = new Router()

// Auth-protected in one line — no manual JWT plumbing
router.get('/me',
  ctx => ctx.json(ctx.get('auth.user')),
  [Authenticate(auth)],
)

await new HttpKernel().useRouter(router).listen(3000)

Why developers pick Pearl

Built for fast backend development. No manual wiring required.

Four reasons Pearl earns a spot in your stack. Each one is backed by a specific feature, not a marketing line.

01Ship faster

Day one, not week one.

npx pearl new my-api scaffolds a complete project — auth routes, validated forms, queue worker, migration setup, .env, hot-reload — already wired. Your first endpoint is minutes away, not "after I pick a router."

npx pearl new my-api && pearl serve

02Typed end-to-end

The compiler is your QA.

Params, query, body, validated FormRequest input, authenticated user, job payloads, dispatched events — all inferred. Rename a column, the compiler tells you every call site that needs updating.

const data = await CreatePostRequest.validate(ctx)
//    ^^ typed from your Zod schema

03Conventions, not decisions

Where does this go? Already answered.

Controllers, requests, jobs, listeners, mailables, migrations — each has a home, and the CLI generates the boilerplate. No bikeshedding, no folder-structure committee.

pearl make:controller Post --resource

04Production-ready

The "for v2" features are already in.

Rate limiting, retry-with-backoff, dead-letter handling, structured 422/403 errors, algorithm-pinned JWT, prototype-pollution-safe job payloads. The stuff you'd normally add after the first outage — already there.

new RateLimit({ windowMs: 15*60_000, max: 5 })

01Routing & Middleware

Routes. Middleware.Typed end-to-end.

Define routes with a fully-typed HttpContext — params, query, body, and the authenticated user, all inferred. Apply middleware with a single array: authentication, logging, rate-limiting. No decorators, no magic.

Read the docs
routes/api.ts
import { Router } from '@pearl-framework/http'
import { Authenticate } from '@pearl-framework/auth'

const router = new Router()

// Public
router.get('/health', ctx =>
  ctx.json({ status: 'ok', ts: Date.now() })
)

// Params, query, body — all typed
router.get('/posts/:id', async ctx => {
  const id   = ctx.param('id')
  const page = ctx.query('page') ?? '1'
  const post = await db.query.posts
    .findFirst({ where: eq(posts.id, Number(id)) })
  return post
    ? ctx.json(post)
    : ctx.json({ error: 'Not found' }, 404)
})

// Protected — Authenticate() is a typed middleware
router.post('/posts', createPost, [Authenticate(auth)])
router.put('/posts/:id',  updatePost, [Authenticate(auth)])

02Database

Drizzle ORM.Built right in.

@pearl-framework/database wires Drizzle directly into the IoC container. Define your schema in TypeScript, query with full autocomplete and type safety, and run migrations with pearl migrate. Supports Postgres, MySQL, and SQLite.

Read the docs
schema/posts.ts
import { pgTable, serial, text, integer, timestamp } from 'drizzle-orm/pg-core'

export const posts = pgTable('posts', {
  id:        serial('id').primaryKey(),
  title:     text('title').notNull(),
  content:   text('content').notNull(),
  authorId:  integer('author_id').references(() => users.id),
  status:    text('status', { enum: ['draft','published'] })
               .notNull().default('draft'),
  createdAt: timestamp('created_at').defaultNow(),
})

// Fully typed, full autocomplete
const post = await db.query.posts.findFirst({
  where: eq(posts.id, Number(ctx.param('id'))),
  with:  { author: true },
})

// $ pearl migrate

03Validation

Zod validation.Before you see it.

Extend FormRequest with a Zod schema. Pearl validates the incoming body before your controller runs — invalid requests get a structured 422 with field-level errors automatically. Nothing leaks through.

Read the docs
requests/CreatePostRequest.ts
import { FormRequest } from '@pearl-framework/validate'
import { z } from 'zod'

export class CreatePostRequest extends FormRequest {
  schema = z.object({
    title:   z.string().min(3).max(120),
    content: z.string().min(10),
    tags:    z.array(z.string()).max(5).optional(),
    status:  z.enum(['draft', 'published']).default('draft'),
  })
}

async function createPost(ctx: HttpContext) {
  const data = await CreatePostRequest.validate(ctx)
  // data is fully typed: { title: string, content: string, ... }
  const [post] = await db.insert(posts).values(data).returning()
  return ctx.json(post, 201)
}

// Bad input auto-rejected:
// HTTP 422 { errors: { title: ['Too short'] } }

04Queues & Events

Background jobs.Decoupled events.

Dispatch slow work to BullMQ Redis workers with one line. Decouple side-effects using typed domain events — fire them from your service, react in dedicated listener classes. No direct imports between layers.

Read the docs
jobs/SendWelcomeEmailJob.ts
import { Job } from '@pearl-framework/queue'
import { Event, Listen, emit } from '@pearl-framework/events'

class SendWelcomeEmailJob extends Job {
  userId!: number

  async handle() {
    await Mail.send(new WelcomeMail(this.userId))
  }
}

class UserRegisteredEvent extends Event {
  constructor(public user: User) { super() }
}

// Fire from your service
await emit(new UserRegisteredEvent(user))

@Listen(UserRegisteredEvent)
class OnUserRegistered {
  async handle({ user }: UserRegisteredEvent) {
    await Queue.dispatch(
      Object.assign(new SendWelcomeEmailJob(), { userId: user.id })
    )
  }
}

05Security

Secure by default.Scaffolded in.

Every Pearl app starts with an explicit CORS allow-list, HSTS + CSP + X-Frame-Options headers, a per-IP rate limiter, a 1 MiB request-body cap, and an error handler that returns generic 500s so framework internals never reach clients. `pearl new` writes the middleware files into your project — wired in `server.ts`, yours to customise.

Read the docs
src/server.ts
import { Router, HttpKernel } from '@pearl-framework/http'
import { ErrorHandler } from './middleware/ErrorHandler.js'
import { SecurityHeaders } from './middleware/SecurityHeaders.js'
import { Cors } from './middleware/Cors.js'
import { apiRateLimit } from './middleware/RateLimit.js'

const router = new Router()

// Order matters — error handler outermost, rate limit innermost
router.use(new ErrorHandler())
router.use(new SecurityHeaders())
router.use(new Cors())
router.use(apiRateLimit)

const kernel = new HttpKernel({
  maxBodyBytes:     1024 * 1024,                  // 1 MiB cap
  onUnhandledError: (err) => apm.report(err),     // never leaks to clients
})

await kernel.useRouter(router).listen(3000)

CLI scaffolding

One command.
Everything generated.

npx pearl new my-api gives you a complete, structured project. .env is created automatically and loaded by Pearl on boot — no dotenv import, no manual wiring.

  • .env auto-created and loaded on boot — no import needed
  • IoC container with constructor injection throughout
  • pearl make:controller, model, job, event, listener, migration
  • pearl serve — hot-reload dev server, zero config
  • pearl migrate — Drizzle migrations from the CLI
~ npx pearl new my-api
my-api/
├── src/
│   ├── controllers/      # HTTP handlers
│   ├── schema/           # Drizzle table defs
│   ├── middleware/       # custom middleware
│   ├── jobs/             # BullMQ background jobs
│   ├── events/           # domain events
│   ├── listeners/        # event listeners
│   ├── mail/             # Mailable classes
│   ├── requests/         # Zod FormRequest validation
│   ├── routes/api.ts     # all your routes
│   ├── database/migrations/
│   ├── providers/        # service providers
│   └── server.ts         # entry point
├── .env                  # auto-created & auto-loaded ✓
├── package.json
└── tsconfig.json

What you'll ship

From npx pearl new to a production API — same afternoon.

Pick the app type on the left — Pearl supports each one with the primitives on the right. Every item on the right is shipped and ready to use today; you don't have to pick libraries or wire them together.

What you can build

  • REST APIs

    Typed routes, typed params, typed bodies. JWT or session auth in two lines, validation before the handler runs.

  • SaaS backends

    Queue background jobs to BullMQ, send transactional mail, fire typed domain events — no glue code between layers.

  • Internal tools

    Role-aware controllers, audit-trail events, fast CLI scaffolding for new models, routes, and migrations.

  • Microservices

    Rate-limited endpoints, structured error responses, ORM-agnostic data layer, and a lean container that starts fast.

What you skip building

  • Validate request bodiesZod-backed FormRequest, typed return
  • Issue & verify JWTs safelyJwtGuard — algorithm pinned, none blocked
  • Cookie-based sessionsSessionGuard with rotation + logout-all
  • Rate-limit /login & /signupRateLimit middleware, pluggable store
  • Lock down CORS originsAllow-list middleware, refuses * by design
  • Set baseline security headersHSTS, CSP, X-Frame-Options scaffolded in
  • Cap incoming request bodiesBuilt-in 1 MiB limit, override per kernel
  • Prevent error-message leaksGeneric 500s; APM hook for the real cause
  • Run background jobsBullMQ queue + retry/backoff helpers
  • Send transactional mailMailer + SMTP/SES + bulk concurrency
  • Fire-and-forget domain eventsTyped dispatcher with onError APM hook
  • Wire the whole thing togetherIoC container, providers, one boot()

What you get

11 packages. One install.

@pearl-framework/pearl is a meta-package that pulls in all 11 below. Each is also available individually if you prefer à la carte.

@pearl/core1.3.0

IoC container, application kernel, service providers

@pearl/http1.3.0

HTTP kernel — router, middleware pipeline, request/response

@pearl/auth1.3.0

JWT, session, and API token authentication guards

@pearl/database1.3.0

Drizzle ORM — Postgres, MySQL, SQLite integration

@pearl/validate1.3.0

Zod-powered FormRequest, validation pipes, error formatting

@pearl/events1.3.0

Type-safe event dispatcher, listeners, queued events

@pearl/queue1.3.0

BullMQ-powered job dispatching, workers, and retries

@pearl/mail1.3.0

Nodemailer-powered mailable classes, transports, queue support

@pearl/testing1.3.0

HTTP test client, database helpers, mail fakes, test utilities

@pearl/cli1.3.0

CLI for scaffolding — new, serve, make:*

@pearl/pearl1.3.0

Meta-package — installs all packages in one command

Start building

Your next API starts
with one command.

npx pearl new my-api scaffolds everything. pearl serve and you're live.

Read the docs View on GitHub