|Docs

Deploy a SaaS Backend with Postgres, Workers, and Webhooks

architecturesaaspostgresworkers

A typical SaaS backend needs an API, a database, background processing, and scheduled tasks. This guide walks through deploying that architecture on Railway as a single project.

Best for: SaaS products that need an API, a database, background job processing, and scheduled tasks in a single project. This pattern fits applications that process webhooks, generate reports, send emails, or run data pipelines alongside the main API.

Architecture overview

This setup uses four components:

  • API service handles HTTP requests and serves your application.
  • Postgres stores application data.
  • Worker service processes background jobs from a Redis queue.
  • Cron service runs scheduled tasks such as webhook retries and data cleanup.

All four run in one Railway project and communicate over private networking. The API and worker share a Redis instance for job dispatch. No direct connection between the API and worker is required.

Prerequisites

  • A Railway account
  • Application code with separate entry points for the API and worker
  • Railway CLI installed (optional)

1. Create the project and database

  1. Create a new project on Railway.
  2. Add a PostgreSQL database: click + New, then Database, then PostgreSQL.
  3. Add a Redis database for the job queue: click + New, then Database, then Redis.

Both databases are available to any service in the project via reference variables.

2. Deploy the API service

  1. Deploy from GitHub or the CLI.
  2. Add reference variables for DATABASE_URL (from Postgres) and REDIS_URL (from Redis). See reference variables.
  3. Generate a public domain under Settings > Networking > Public Networking.
  4. Configure a pre-deploy command to run database migrations before each deployment.

3. Deploy the worker service

  1. Add another service to the project: click + New, then select your GitHub repo or a Docker image.
  2. Point it at the same repo but set a different start command (e.g., node worker.js or python worker.py).
  3. Add the same DATABASE_URL and REDIS_URL reference variables.
  4. Do not assign a public domain. The worker communicates via Redis and private networking only.

Configuring the start command

If your API and worker share the same repository, differentiate them with the start command:

  • API service: node server.js (or your framework's start command)
  • Worker service: node worker.js

Set the start command in Settings > Deploy > Start Command for each service.

4. Add a cron job for scheduled tasks

  1. Add another service to the project.
  2. Set the start command to your scheduled task script (e.g., node cron/retry-webhooks.js).
  3. Go to Settings > Cron Schedule and set a crontab expression (e.g., */5 * * * * for every 5 minutes). The minimum cron frequency is every 5 minutes, and all schedules are evaluated in UTC.
  4. Add the same DATABASE_URL and REDIS_URL reference variables.

The cron service starts on schedule, executes the task, and exits. See Cron Jobs for details on scheduling and failure behavior.

5. Configure webhook processing

A common SaaS pattern is receiving webhooks from third-party services (Stripe, GitHub, etc.), enqueuing them for background processing, and retrying failures on a schedule.

The flow:

  1. Your API receives an incoming webhook POST request.
  2. It validates the webhook signature.
  3. It enqueues the payload in Redis.
  4. It returns a 200 response immediately.
  5. The worker picks up the job and processes it.
  6. The cron service retries failed webhooks on a schedule.

This pattern keeps webhook processing out of the request path so your API stays responsive. Failed webhooks are retried automatically without manual intervention.

How the services connect

ServiceConnects toVia
APIPostgresDATABASE_URL reference variable
APIRedisREDIS_URL reference variable
WorkerPostgresDATABASE_URL reference variable
WorkerRedisREDIS_URL reference variable
CronPostgresDATABASE_URL reference variable
CronRedisREDIS_URL reference variable
APIWorkerRedis queue (no direct connection needed)

All inter-service database connections use Railway's private networking automatically when services are in the same project.

Next steps