Deploy a SaaS Backend with Postgres, Workers, and Webhooks
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
- Create a new project on Railway.
- Add a PostgreSQL database: click + New, then Database, then PostgreSQL.
- 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
- Deploy from GitHub or the CLI.
- Add reference variables for
DATABASE_URL(from Postgres) andREDIS_URL(from Redis). See reference variables. - Generate a public domain under Settings > Networking > Public Networking.
- Configure a pre-deploy command to run database migrations before each deployment.
3. Deploy the worker service
- Add another service to the project: click + New, then select your GitHub repo or a Docker image.
- Point it at the same repo but set a different start command (e.g.,
node worker.jsorpython worker.py). - Add the same
DATABASE_URLandREDIS_URLreference variables. - 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
- Add another service to the project.
- Set the start command to your scheduled task script (e.g.,
node cron/retry-webhooks.js). - 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. - Add the same
DATABASE_URLandREDIS_URLreference 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:
- Your API receives an incoming webhook POST request.
- It validates the webhook signature.
- It enqueues the payload in Redis.
- It returns a
200response immediately. - The worker picks up the job and processes it.
- 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
| Service | Connects to | Via |
|---|---|---|
| API | Postgres | DATABASE_URL reference variable |
| API | Redis | REDIS_URL reference variable |
| Worker | Postgres | DATABASE_URL reference variable |
| Worker | Redis | REDIS_URL reference variable |
| Cron | Postgres | DATABASE_URL reference variable |
| Cron | Redis | REDIS_URL reference variable |
| API | Worker | Redis 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
- Cron Jobs - Scheduling, failure modes, and crontab expressions
- PostgreSQL on Railway - Provisioning and connection details
- Redis on Railway - Provisioning and connection details
- Private Networking - How services communicate internally
- Pre-deploy Command - Running migrations before deployment