Agent Skills: Replit Core Workflow A — Full-Stack App

|

UncategorizedID: jeremylongshore/claude-code-plugins-plus-skills/replit-core-workflow-a

Install this agent skill to your local

pnpm dlx add-skill https://github.com/jeremylongshore/claude-code-plugins-plus-skills/tree/HEAD/plugins/saas-packs/replit-pack/skills/replit-core-workflow-a

Skill Files

Browse the full folder contents for replit-core-workflow-a.

Download Skill

Loading file tree…

plugins/saas-packs/replit-pack/skills/replit-core-workflow-a/SKILL.md

Skill Metadata

Name
replit-core-workflow-a
Description
|

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.