Replit Core Workflow A โ Full-Stack App
Overview
Build a production-ready web app on Replit: Express or Flask server, PostgreSQL database, Replit Auth for user login, Object Storage for file uploads, and Autoscale deployment. This is the primary money-path workflow for shipping apps on Replit.
Prerequisites
- Replit account (Core plan or higher for deployments)
.replit and replit.nix configured (see replit-install-auth)
- PostgreSQL provisioned in the Database pane
Instructions
Step 1: Project Structure
my-app/
โโโ .replit # Run + deployment config
โโโ replit.nix # System dependencies
โโโ package.json
โโโ tsconfig.json
โโโ src/
โ โโโ index.ts # Express entry point
โ โโโ routes/
โ โ โโโ api.ts # API endpoints
โ โ โโโ auth.ts # Auth routes
โ โ โโโ health.ts # Health check
โ โโโ services/
โ โ โโโ db.ts # PostgreSQL pool
โ โ โโโ storage.ts # Object Storage
โ โโโ middleware/
โ โโโ auth.ts # Replit Auth middleware
โ โโโ errors.ts # Error handler
โโโ tests/
Step 2: Configuration Files
entrypoint = "src/index.ts"
run = "npx tsx src/index.ts"
modules = ["nodejs-20:v8-20230920-bd784b9"]
[nix]
channel = "stable-24_05"
[env]
NODE_ENV = "development"
[deployment]
run = ["sh", "-c", "npx tsx src/index.ts"]
build = ["sh", "-c", "npm ci"]
deploymentTarget = "autoscale"
# replit.nix
{ pkgs }: {
deps = [
pkgs.nodejs-20_x
pkgs.nodePackages.typescript-language-server
pkgs.postgresql
];
}
Step 3: Database Layer
import { Pool } from 'pg';
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: { rejectUnauthorized: false },
max: 10,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 5000,
});
export async function initDB() {
await pool.query(`
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
profile_image TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS posts (
id SERIAL PRIMARY KEY,
user_id TEXT REFERENCES users(id),
title TEXT NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
`);
}
export async function upsertUser(id: string, username: string, image: string) {
return pool.query(
`INSERT INTO users (id, username, profile_image)
VALUES ($1, $2, $3)
ON CONFLICT (id) DO UPDATE SET username = $2, profile_image = $3
RETURNING *`,
[id, username, image]
);
}
export async () {
pool.(
,
[userId, title, content]
);
}
() {
pool.(
,
[limit]
);
}
{ pool };
Step 4: Auth Middleware
import { Request, Response, NextFunction } from 'express';
import { upsertUser } from '../services/db';
export interface AuthedRequest extends Request {
user: { id: string; name: string; image: string };
}
export async function requireAuth(req: Request, res: Response, next: NextFunction) {
const userId = req.headers['x-replit-user-id'] as string;
if (!userId) return res.status(401).json({ error: 'Login required' });
const name = (req.headers['x-replit-user-name'] as string) || '';
const image = (req.headers['x-replit-user-profile-image'] ) || ;
(userId, name, image);
(req ). = { : userId, name, image };
();
}
Step 5: API Routes
import { Router } from 'express';
import { requireAuth, AuthedRequest } from '../middleware/auth';
import { createPost, getPosts } from '../services/db';
import { Client as StorageClient } from '@replit/object-storage';
const router = Router();
const storage = new StorageClient();
router.get('/posts', async (req, res) => {
const { rows } = await getPosts();
res.json(rows);
});
router.post('/posts', requireAuth, async (req, res) => {
const { title, content } = req.body;
const user = (req as AuthedRequest).user;
const { rows } = await createPost(user.id, title, content);
res.status(201).json(rows[0]);
});
router.(, requireAuth, (req, res) => {
user = (req ).;
filename = ;
storage.(filename, req..);
res.({ : filename });
});
router;
Step 6: Entry Point
import express from 'express';
import { initDB, pool } from './services/db';
import apiRoutes from './routes/api';
const app = express();
app.use(express.json());
app.get('/health', async (req, res) => {
try {
await pool.query('SELECT 1');
res.json({ status: 'ok', uptime: process.uptime() });
} catch {
res.status(503).json({ status: 'degraded' });
}
});
app.get('/api/me', (req, res) => {
const id = req.headers['x-replit-user-id'];
if (!id) return res.json({ loggedIn: false });
res.json({
loggedIn: true,
id,
name: req.[],
: req.[],
});
});
app.(, apiRoutes);
= (process.. || );
().( {
app.(, , {
.();
});
});
Error Handling
| Error | Cause | Solution |
|---|
| DATABASE_URL undefined | PostgreSQL not provisioned | Create database in Database pane |
| Auth headers empty | Running in dev mode | Auth only works on deployed .replit.app |
| Object Storage 403 | No bucket created | Provision bucket in Object Storage pane |
| Port conflict | Multiple services on same port | Use different ports, set ignorePorts |
Resources
Next Steps
For collaboration and admin workflows, see replit-core-workflow-b.