replit-core-workflow-a

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".

claude-codecodexopenclaw
6 Tools
replit-pack Plugin
saas packs Category

Allowed Tools

ReadWriteEditBash(npm:*)Bash(pip:*)Grep

Provided by Plugin

replit-pack

Claude Code skill pack for Replit (30 skills)

saas packs v1.0.0
View Plugin

Installation

This skill is included in the replit-pack plugin:

/plugin install replit-pack@claude-code-plugins-plus

Click to copy

Instructions

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


# .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"

# replit.nix
{ pkgs }: {
  deps = [
    pkgs.nodejs-20_x
    pkgs.nodePackages.typescript-language-server
    pkgs.postgresql
  ];
}

Step 3: Database Layer


// 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


// 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


// 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


// 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

Next Steps

For collaboration and admin workflows, see replit-core-workflow-b.

Ready to use replit-pack?