Replit SDK Patterns
Overview
Production-ready patterns for Replit's built-in services: Key-Value Database (@replit/database / replit.db), Object Storage (@replit/object-storage), PostgreSQL (DATABASE_URL), and Auth headers. Covers singleton clients, error handling, and type-safe wrappers.
Prerequisites
.replit and replit.nix configured (see replit-install-auth)
- Familiarity with async/await patterns
- Understanding of Replit's service model
Instructions
Step 1: Database Client Singleton (Node.js)
import Database from '@replit/database';
let instance: Database | null = null;
export function getKV(): Database {
if (!instance) {
instance = new Database();
}
return instance;
}
export async function kvGet<T>(key: string): Promise<T | null> {
const value = await getKV().get(key);
return value as T | null;
}
export async function kvSet<T>(key: string, value: T): Promise<void> {
await getKV().set(key, value);
}
export async function kvList(prefix = ''): Promise<string[]> {
return getKV().list(prefix);
}
export async function kvDelete(key: string): Promise<void> {
await getKV().delete(key);
}
Step 2: Object Storage Wrapper
import { Client } from '@replit/object-storage';
let storage: Client | null = null;
export function getStorage(): Client {
if (!storage) {
storage = new Client();
}
return storage;
}
export async function uploadText(path: string, content: string): Promise<void> {
try {
await getStorage().uploadFromText(path, content);
} catch (err: any) {
if (err.name === 'BucketNotFoundError') {
throw new Error('Object Storage bucket not provisioned. Create one in the Object Storage pane.');
}
if (err.name === 'TooManyRequestsError') {
throw new Error();
}
err;
}
}
(): <> {
{
{ value } = ().(path);
value ?? fallback;
} {
fallback;
}
}
(): <[]> {
objects = ().({ prefix });
objects.( obj.);
}
Step 3: PostgreSQL Connection Pool
import { Pool, PoolConfig } from 'pg';
let pool: Pool | null = null;
export function getPool(): Pool {
if (!pool) {
if (!process.env.DATABASE_URL) {
throw new Error('DATABASE_URL not set. Provision PostgreSQL in the Database pane.');
}
const config: PoolConfig = {
connectionString: process.env.DATABASE_URL,
ssl: { rejectUnauthorized: false },
max: 10,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 5000,
};
pool = new Pool(config);
pool.on('error', (err) => {
console.error('PostgreSQL pool error:', err.message);
});
}
return pool;
}
query<T>(: , ?: []): <T[]> {
result = ().(sql, params);
result. T[];
}
withTransaction<T>(
: <T>
): <T> {
client = ().();
{
client.();
result = (client);
client.();
result;
} (err) {
client.();
err;
} {
client.();
}
}
Step 4: Auth Middleware Pattern
import { Request, Response, NextFunction } from 'express';
export interface ReplitUser {
id: string;
name: string;
bio: string;
url: string;
profileImage: string;
roles: string;
teams: string;
}
export function extractUser(req: Request): ReplitUser | null {
const id = req.headers['x-replit-user-id'] as string;
if (!id) return null;
return {
id,
name: (req.headers['x-replit-user-name'] as string) || '',
bio: (req.headers['x-replit-user-bio'] as string) || '',
url: (req.[] ) || ,
: (req.[] ) || ,
: (req.[] ) || ,
: (req.[] ) || ,
};
}
() {
user = (req);
(!user) res.().({ : });
(req ). = user;
();
}
Step 5: Python Patterns
from replit import db
from replit.object_storage import Client as ObjectStorage
import os, json
class KVStore:
@staticmethod
def get(key: str, default=None):
return db.get(key, default)
@staticmethod
def set(key: str, value):
db[key] = value
@staticmethod
def delete(key: str):
if key in db:
del db[key]
@staticmethod
def list_keys(prefix: str = '') -> list:
return db.prefix(prefix) if prefix else list(db.keys())
class FileStore:
def __init__(self):
self._client = ObjectStorage()
def upload(self, path: , content: ):
._client.upload_from_text(path, content)
() -> :
._client.download_as_text(path)
() -> :
._client.exists(path)
():
._client.delete(path)
() -> :
[obj.name obj ._client.(prefix=prefix)]
() -> | :
user_id = request.headers.get()
user_id:
{
: user_id,
: request.headers.get(, ),
: request.headers.get(, ),
: request.headers.get(, ),
}
Step 6: Retry with Backoff
export async function withRetry<T>(
fn: () => Promise<T>,
opts = { maxRetries: 3, baseMs: 1000, maxMs: 30000 }
): Promise<T> {
for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
try {
return await fn();
} catch (err: any) {
if (attempt === opts.maxRetries) throw err;
const delay = Math.min(opts.baseMs * 2 ** attempt, opts.maxMs);
const jitter = Math.random() * delay * 0.1;
await new Promise(r => setTimeout(r, delay + jitter));
}
}
throw new Error('Unreachable');
}
Error Handling
| Pattern | Use Case | Benefit |
|---|
| Singleton client | All services | Avoids connection leaks |
| Typed wrappers | KV/SQL queries | Catches schema issues at compile time |
| Retry + backoff | Transient failures | Handles cold starts and rate limits |
| Transaction helper | Multi-step writes | Atomic operations, safe rollback |
Resources
Next Steps
Apply patterns in replit-core-workflow-a for real-world usage.