Overview
Putnami frameworks are composable plugins. You only bring the parts you need for each app, and the CLI orchestrates them together. This modular architecture lets you start simple and add capabilities as your application grows.
Packages at a glance
- @putnami/application - HTTP servers, middleware, API routing, WebSockets, sessions, OAuth, static files
- @putnami/react - React SSR, file-based routing, loaders, actions, streaming, hydration
- @putnami/sql - PostgreSQL integration, entities, repositories, migrations, queries
- @putnami/storage - Object storage, bucket declarations, presigned URLs, pluggable backends
- @putnami/ui - UI components and theming, design system, accessible components
- @putnami/runtime - Foundation utilities, DI container, config, logging, validation
Application composition
Every Putnami application starts with an Application instance. You compose functionality by chaining .use() calls with plugins and modules. Prefer the application() factory:
import { application, http, api, statics } from '@putnami/application';
import { react } from '@putnami/react';
import { sql } from '@putnami/sql';
export const app = () =>
application()
.use(http({ port: 3000 }))
.use(api())
.use(react())
.use(sql())
.use(statics({ publicFolder: 'public' }));Modules
Group related plugins into reusable modules with module(). The hierarchy is Application > Module > Plugin:
import { application, module, http, api, oAuth2 } from '@putnami/application';
const authModule = module('auth')
.use(oAuth2())
.use(api({ prefix: '/auth' }));
export const app = () =>
application()
.use(http({ port: 3000 }))
.use(authModule);Modules are composable — they can contain plugins, sub-modules, and DI providers. Only the Application has a startable lifecycle; modules are structural units.
Plugin lifecycle
Each plugin can hook into four lifecycle phases:
- generate() - Runs at build time for code generation and asset compilation
- warmup() - Runs before the app starts for route registration and cache preloading
- start() - Starts servers, opens database connections, initializes background tasks
- stop() - Graceful shutdown in reverse order of registration
Architecture patterns
Full-stack web application
For applications with a React frontend and API backend:
import { application, http, api, statics, oAuth2 } from '@putnami/application';
import { react } from '@putnami/react';
import { sql } from '@putnami/sql';
export const app = () =>
application()
.use(http({ port: 3000 }))
.use(sql())
.use(oAuth2())
.use(api())
.use(react())
.use(statics({ publicFolder: 'public' }));API-only service
For microservices or backend APIs without a frontend:
import { application, http, api } from '@putnami/application';
import { sql } from '@putnami/sql';
export const app = () =>
application()
.use(http({ port: 3000 }))
.use(sql())
.use(api());Background job runner
For one-shot jobs or scheduled tasks:
import { application } from '@putnami/application';
import { sql } from '@putnami/sql';
export const job = () =>
application()
.use(sql())
.run(async () => {
// Perform your job logic here
console.log('Job completed');
});How to choose packages
Building a web UI
Start with @putnami/react and @putnami/ui:
import { application, http, statics } from '@putnami/application';
import { react } from '@putnami/react';
export const app = () =>
application()
.use(http())
.use(react())
.use(statics());Key features:
- Server-side rendering with React 19 streaming
- File-based routing in
src/app/ - Typed loaders for data fetching
- Actions for form handling
- Document helpers for SEO
Building an API
Start with @putnami/application and the api() plugin:
import { application, http, api } from '@putnami/application';
export const app = () =>
application()
.use(http({ port: 3000 }))
.use(api());Key features:
- File-based route discovery
- Request context with typed body parsing
- Response helpers for JSON, redirects, errors
- Middleware composition
- WebSocket support
Adding persistence
Add @putnami/sql for PostgreSQL support:
import { application } from '@putnami/application';
import { sql } from '@putnami/sql';
export const app = () =>
application()
.use(sql());Key features:
- Entity decorators (
@Table,@Column,@Key) - Repository pattern with type-safe queries
- Migration management
- Multi-database support
- Query operators for complex filters
Adding authentication
Add the oAuth2() plugin for OAuth2/OIDC support:
import { application, http, oAuth2 } from '@putnami/application';
export const app = () =>
application()
.use(http())
.use(oAuth2());Key features:
- OAuth2 provider integration
- Session management
- Token handling
- Protected route helpers
Shared concerns
Use @putnami/runtime for cross-cutting functionality:
import { useLogger, useConfig } from '@putnami/runtime';
class MyService {
private logger = useLogger('MyService');
doWork() {
this.logger.info('Working...');
}
}Key features:
- Dependency injection with singleton and request scopes
- Typed configuration with validation
- Structured logging
- Error handling utilities
Project structure
A typical Putnami project follows this structure:
my-app/
src/
main.ts # Application entry point
app/ # Routes and pages
page.tsx # Homepage (React)
loader.ts # Homepage data loader
api/
health/
get.ts # GET /api/health
users/
get.ts # GET /api/users
post.ts # POST /api/users
[id]/
get.ts # GET /api/users/:id
entities/ # Database entities
user.ts
services/ # Business logic
user.service.ts
lib/ # Utilities
public/ # Static assets
.env.local.yaml # Local configuration
package.json
tsconfig.jsonNext steps
Dive deeper into each framework capability:
- Web - React SSR and file-based routing
- API - HTTP routes and request handling
- Auth - OAuth2 and authentication
- Persistence - Database entities and queries
- Storage - Object storage and file uploads
- Configuration - Typed config management
- Plugins & Lifecycle - Custom plugins
- HTTP & Middleware - Request processing
- Dependency Injection - Service management