'Build a full-stack web app on Replit with Express/Flask, PostgreSQL,
Scanned 9/2/2026
Install to Claude Code
npx -y skills add jeremylongshore/tons-of-skills-marketplace --skill replit-core-workflow-a --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Replit Core Workflow A?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/jeremylongshore-replit-core-workflow-a-tons-of-skills-marketplace)More formats (shields.io, HTML) on the badges page.
---
name: replit-core-workflow-a
description: 'Build a full-stack web app on Replit with Express/Flask, PostgreSQL,
Auth, and deployment.
Use when creating a new production app on Replit from scratch,
building the primary user-facing workflow, or following Replit best practices.
Trigger with phrases like "build replit app", "replit full stack",
"replit web app", "create replit project", "replit express flask".
'
allowed-tools: Read, Write, Edit, Bash(npm:*), Bash(pip:*), Grep
version: 1.13.0
license: MIT
author: Jeremy Longshore <jeremy@intentsolutions.io>
tags:
- saas
- replit
- workflow
- full-stack
compatibility: Designed for Claude Code
---
# 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
```toml
# .replit
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"
```
```nix
# replit.nix
{ pkgs }: {
deps = [
pkgs.nodejs-20_x
pkgs.nodePackages.typescript-language-server
pkgs.postgresql
];
}
```
### Step 3: Database Layer
```typescript
// src/services/db.ts
import { Pool } from 'pg';
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: { rejectUnauthorized: false },
max: 10,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 5000,
});
// Initialize schema
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 function createPost(userId: string, title: string, content: string) {
return pool.query(
'INSERT INTO posts (user_id, title, content) VALUES ($1, $2, $3) RETURNING *',
[userId, title, content]
);
}
export async function getPosts(limit = 20) {
return pool.query(
`SELECT p.*, u.username, u.profile_image
FROM posts p JOIN users u ON p.user_id = u.id
ORDER BY p.created_at DESC LIMIT $1`,
[limit]
);
}
export { pool };
```
### Step 4: Auth Middleware
```typescript
// src/middleware/auth.ts
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'] as string) || '';
// Upsert user in database on every authenticated request
await upsertUser(userId, name, image);
(req as any).user = { id: userId, name, image };
next();
}
```
### Step 5: API Routes
```typescript
// src/routes/api.ts
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();
// Public: list posts
router.get('/posts', async (req, res) => {
const { rows } = await getPosts();
res.json(rows);
});
// Protected: create post
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]);
});
// Protected: upload file to Object Storage
router.post('/upload', requireAuth, async (req, res) => {
const user = (req as AuthedRequest).user;
const filename = `uploads/${user.id}/${Date.now()}-${req.body.name}`;
await storage.uploadFromText(filename, req.body.content);
res.json({ path: filename });
});
export default router;
```
### Step 6: Entry Point
```typescript
// src/index.ts
import express from 'express';
import { initDB, pool } from './services/db';
import apiRoutes from './routes/api';
const app = express();
app.use(express.json());
// Health check (required for Replit deployments)
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' });
}
});
// Auth info endpoint (client-side: GET /__replauthuser)
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.headers['x-replit-user-name'],
image: req.headers['x-replit-user-profile-image'],
});
});
app.use('/api', apiRoutes);
const PORT = parseInt(process.env.PORT || '3000');
initDB().then(() => {
app.listen(PORT, '0.0.0.0', () => {
console.log(`Server running on port ${PORT}`);
});
});
```
## 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
- [Replit Deployments](https://docs.replit.com/cloud-services/deployments/reserved-vm-deployments)
- [PostgreSQL on Replit](https://docs.replit.com/cloud-services/storage-and-databases/postgresql-on-replit)
- [Replit Auth](https://docs.replit.com/replit-workspace/replit-auth)
- [Object Storage](https://docs.replit.com/cloud-services/storage-and-databases/object-storage/overview)
## Next Steps
For collaboration and admin workflows, see `replit-core-workflow-b`.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!