This project is a backend system designed to emulate core functionalities of Zapier, focusing on webhook-triggered automation. It allows users to define workflows (Zaps) where incoming webhooks trigger a series of predefined actions. The system is built with a microservices architecture using Node.js, TypeScript, PostgreSQL, and Kafka to handle asynchronous task processing.
The system is designed with a microservices architecture to handle webhook processing and action execution in a scalable and resilient manner. The key components are:
-
primary-backend:- Role: The main API for user interactions.
- Responsibilities: Handles user authentication, creation and management of Zaps (workflows), defining triggers (e.g., which webhook to listen to), and the sequence of actions for each Zap. All configurations are stored in the shared PostgreSQL database.
-
hooks:- Role: Responsible for receiving all incoming webhooks.
- Responsibilities: When an external service sends a request to a registered webhook URL, this service captures the data. It then creates a
ZapRunrecord (representing a specific instance of a Zap execution) and an entry in theZapRunOutboxtable in PostgreSQL. This outbox pattern ensures that the intention to run a Zap is durably stored before being passed to the asynchronous processing pipeline.
-
processor:- Role: Acts as a bridge between the initial webhook reception and the action execution.
- Responsibilities: This service polls (or is triggered by) the
ZapRunOutboxtable. For each new entry, it retrieves the fullZapRundetails and the correspondingZapconfiguration (which includes the list of actions to perform). It then breaks down the Zap into individual action tasks and publishes these tasks as messages to thezap-eventsKafka topic. Once messages are successfully sent to Kafka, the entry inZapRunOutboxis typically marked as processed or removed.
-
worker:- Role: Executes the individual actions defined in a Zap.
- Responsibilities: Workers subscribe to the
zap-eventsKafka topic where action tasks are published by theprocessor. Each worker consumes these messages and performs the designated action. This decoupled design allows for multiple workers to process actions in parallel, providing scalability.
-
PostgreSQL Database:
- A shared relational database used by
primary-backend,hooks, andprocessor. - Stores user information, Zap definitions (triggers, actions, sequences),
ZapRunhistory, and theZapRunOutboxtable for reliable event handoff.
- A shared relational database used by
-
Apache Kafka:
- A distributed streaming platform used as a message broker.
- Decouples the
processor(which decides what actions to run) from theworkerservices (which execute those actions). This improves fault tolerance and allows for independent scaling of components. The primary topic used iszap-events.
This section guides you through setting up and running the entire system using Docker.
- Docker installed and running.
- A terminal or command prompt.
First, create a dedicated Docker network for the services to communicate with each other:
docker network create zapier-networkRun a PostgreSQL container:
docker run -d --name postgres-db --network zapier-network -e POSTGRES_USER=myuser -e POSTGRES_PASSWORD=mypassword -e POSTGRES_DB=zapierdb -p 5432:5432 -v postgres_data:/var/lib/postgresql/data postgres:13-alpine- This command starts a PostgreSQL 13 instance.
myuser,mypassword, andzapierdbare example credentials. Change them if needed, and update theDATABASE_URLin subsequent steps accordingly.- Data will be persisted in a Docker volume named
postgres_data. - The database will be accessible on
localhost:5432from your host machine. - The
DATABASE_URLfor the services will be:postgresql://myuser:mypassword@postgres-db:5432/zapierdb
ZooKeeper (required by Kafka):
docker run -d --name zookeeper --network zapier-network -p 2181:2181 confluentinc/cp-zookeeper:latest -e ZOOKEEPER_CLIENT_PORT=2181 -e ZOOKEEPER_TICK_TIME=2000Kafka:
docker run -d --name kafka --network zapier-network -p 9092:9092 -e KAFKA_BROKER_ID=1 -e KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 -e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:9092 -e KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT -e KAFKA_INTER_BROKER_LISTENER_NAME=PLAINTEXT -e KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 confluentinc/cp-kafka:latest- Wait for ZooKeeper to start before starting Kafka.
- Kafka will be accessible on
localhost:9092from your host and askafka:9092within thezapier-network. - The
KAFKA_BROKERSenvironment variable for services will bekafka:9092.
For each of the Node.js services (primary-backend, hooks, processor, worker), you'll need a Dockerfile.
Common Dockerfile for Node.js Services:
Create a file named Dockerfile in the root directory of each service (primary-backend/Dockerfile, hooks/Dockerfile, etc.) with the following content:
# Use an official Node.js runtime as a parent image
FROM node:18-alpine
# Set the working directory in the container
WORKDIR /usr/src/app
# Copy package.json and package-lock.json
COPY package*.json ./
# Install project dependencies (including devDependencies for tsc and prisma)
RUN npm install
# Bundle app source
COPY . .
# Build TypeScript
RUN npx tsc -b
# Default command (will be overridden by some services if they don't expose ports)
CMD ["node", "dist/index.js"]Important Environment Variables:
DATABASE_URL:postgresql://myuser:mypassword@postgres-db:5432/zapierdb(for services needing DB access)KAFKA_BROKERS:kafka:9092(for services needing Kafka access)PORT: Specific toprimary-backendandhooks.JWT_PASSWORD: Forprimary-backend.
a. Database Initialization & Migrations
Before starting the application services that depend on the database schema, apply Prisma migrations. You can do this using the image built for any service that includes Prisma (e.g., primary-backend).
First, build the primary-backend image (instructions below). Then run:
docker run --rm --network zapier-network -e DATABASE_URL="postgresql://myuser:mypassword@postgres-db:5432/zapierdb" primary-backend-app npx prisma migrate deploy(Ensure primary-backend-app image is built before running this command).
b. primary-backend Service
- Navigate to the
primary-backenddirectory. - Create the
Dockerfileas specified above. - Build the Docker image:
docker build -t primary-backend-app . - Run the Docker container:
(Exposes on host port 3001, internal container port 3001)
docker run -d --name primary-backend-service --network zapier-network -p 3001:3001 -e DATABASE_URL="postgresql://myuser:mypassword@postgres-db:5432/zapierdb" -e JWT_PASSWORD="your_very_secret_jwt_password" -e PORT="3001" primary-backend-app
c. hooks Service
- Navigate to the
hooksdirectory. - Create the
Dockerfileas specified above. - Modify the
Dockerfile'sCMDtoCMD ["node", "dist/index.js"](if not already default) and you can addEXPOSE 3002for clarity. - Build the Docker image:
docker build -t hooks-app . - Run the Docker container:
(Exposes on host port 3002, internal container port 3002)
docker run -d --name hooks-service --network zapier-network -p 3002:3002 -e DATABASE_URL="postgresql://myuser:mypassword@postgres-db:5432/zapierdb" -e PORT="3002" hooks-app
d. processor Service
- Navigate to the
processordirectory. - Create the
Dockerfileas specified above.CMD ["node", "dist/index.js"]is appropriate. - Build the Docker image:
docker build -t processor-app . - Run the Docker container:
docker run -d --name processor-service --network zapier-network -e DATABASE_URL="postgresql://myuser:mypassword@postgres-db:5432/zapierdb" -e KAFKA_BROKERS="kafka:9092" processor-app
e. worker Service
- Navigate to the
workerdirectory. - Create the
Dockerfileas specified above.CMD ["node", "dist/index.js"]is appropriate. - Build the Docker image:
docker build -t worker-app . - Run the Docker container:
docker run -d --name worker-service --network zapier-network -e KAFKA_BROKERS="kafka:9092" worker-app
- Create
zapier-network. - Start
postgres-db. Wait for it to be ready. - Build
primary-backend-appimage. - Run Prisma migrations using the
primary-backend-appimage. - Start
zookeeper. Wait for it to be ready. - Start
kafka. Wait for it to be ready. - Build and run
primary-backend-service. - Build and run
hooks-service. - Build and run
processor-service. - Build and run
worker-service.
- View logs:
docker logs <container_name_or_id> - Stop a service:
docker stop <container_name_or_id> - Start a stopped service:
docker start <container_name_or_id> - Remove a service:
docker rm <container_name_or_id>(must be stopped first) - Remove network:
docker network rm zapier-network(all containers using it must be stopped and removed) - Remove volume (PostgreSQL data):
docker volume rm postgres_data(be careful, this deletes data)
This comprehensive README should provide all necessary instructions.