Back to blogs
GraphQLFederationArchitectureAWSNestJS

Apr 2026

Federated GraphQL in a Turborepo

I implemented GraphQL federation in my Turborepo by letting domain services own their own schemas and resolvers, then composing them into one unified graph so frontend clients could consume a single API instead of talking to multiple backends directly.

View live project
AWS Deployment Architecture
Internet / ClientHTTPSApplication Load BalancerHTTPS :443 · ACM Certificate · HTTP → HTTPS redirectECS Fargate Cluster · turbo-graphql-productionApollo Router:4000 · supergraph.graphqlCloud Map DNS · *.turbo-graphql.localusers-serviceNestJS · :4001posts-serviceNestJS · :4002comments-serviceNestJS · :4003RDS · users DBPostgreSQL · PrismaRDS · posts DBPostgreSQL · PrismaRDS · comments DBPostgreSQL · PrismaAmazon ECR4 repos · Docker imagesSecrets ManagerDATABASE_URL per serviceCloudWatch Logs/ecs/turbo-graphql/…all 4traffic flowimage pull (ECR)secrets injectionlogs (CloudWatch)cluster boundary

ECS Fargate

Runs all 4 services as tasks

ALB + ACM

HTTPS entry point, TLS termination

ECR

4 Docker image repositories

RDS PostgreSQL

One database per subgraph

Secrets Manager

DATABASE_URL injected at runtime

Cloud Map

Private DNS for router → subgraphs

CloudWatch Logs

Log groups per service, 14-day retention

VPC + Security Groups

ALB SG (public) + ECS SG (internal)

IAM

Task execution role for ECR & Secrets

The problem federation solved

As the system grew, I did not want one large GraphQL service to become the bottleneck for every domain change. Different backend capabilities needed clear ownership, but I still wanted the frontend to see one consistent graph.

Federation was the right fit because it let me split the graph into domain-oriented subgraphs while preserving a single entry point for clients.

How I approached the implementation

Inside the Turborepo, I kept the services organized so each domain could define the part of the schema it truly owned. That meant types, resolvers, and business logic stayed close to the service that knew the data best.

The federated layer then composed those subgraphs into one graph. From the client perspective, it looked like one API. Internally, the ownership was distributed, which made the architecture easier to scale than a single centralized schema.

What made the setup practical

The monorepo structure helped a lot here. Shared tooling made it easier to keep schema conventions, dependencies, and developer workflows aligned across the federated services.

Because everything lived in the same Turborepo, I could evolve subgraphs in parallel, manage changes with better visibility, and reduce the friction that usually appears when multiple services need to contribute to one API surface.

The outcome

The final result was a cleaner separation of backend responsibilities with a much simpler experience for consumers of the API. Frontend applications could query one graph, while backend services remained independently owned and easier to extend.

That is what made federation valuable in this project: it balanced autonomy and consistency. Teams or domains could move independently, but the product still exposed one coherent GraphQL contract.

Running locally

1. Clone the repository and install dependencies: git clone https://github.com/raydedon/turbo-repo-project && cd turbo-repo-project && npm install

2. Copy the environment template and fill in your local database URLs: cp .env.example .env — set DATABASE_URL for each of the three services (users-service, posts-service, comments-service) pointing to local PostgreSQL instances.

3. Run database migrations for all three services from the repo root: npx turbo run migrate — this runs Prisma migrate dev inside each service workspace.

4. Start all services in parallel with: npx turbo run dev — the users-service starts on :4001, posts-service on :4002, and comments-service on :4003.

5. Start the Apollo Router last: cd apps/router && npx router --config router.yaml --supergraph supergraph.graphql — the unified GraphQL API is available at http://localhost:4000/graphql.

6. Open Apollo Sandbox at http://localhost:4000 to explore the federated graph. You can query types owned by any subgraph through the single router endpoint.

Deploying to AWS

1. Push Docker images to ECR — build and tag an image for each service (users-service, posts-service, comments-service) and the Apollo Router, then push all four to their respective ECR repositories in your AWS account.

2. Provision RDS PostgreSQL — create three db.t3.micro PostgreSQL instances in a private subnet (one per subgraph). Store each DATABASE_URL in AWS Secrets Manager under a path like /turbo-graphql/users/database-url so the ECS tasks can fetch it at startup.

3. Set up the VPC and security groups — create an ALB security group that allows inbound HTTPS on :443 from the internet, and an ECS security group that allows inbound traffic only from the ALB SG on the service ports (4000–4003).

4. Register ECS task definitions — one task definition per service and one for the Apollo Router. Each definition references the ECR image URI, sets the port mapping, and includes a Secrets Manager secret binding for DATABASE_URL.

5. Create the ECS Fargate cluster (turbo-graphql-production) and launch four services inside it — one for each task definition. Enable AWS Cloud Map for service discovery so the router can reach subgraphs via private DNS (e.g. users.turbo-graphql.local:4001).

6. Configure the Application Load Balancer — create an HTTPS listener on :443 using an ACM certificate, add an HTTP-to-HTTPS redirect rule on :80, and forward all traffic to the Apollo Router target group on port 4000.

7. Update the Apollo Router supergraph config — replace localhost subgraph URLs with the Cloud Map DNS names, rebuild the supergraph schema, and redeploy the router service to pick up the new config.

8. Verify the deployment — open the ALB DNS name in a browser and run a test query through Apollo Sandbox. Check CloudWatch log groups under /ecs/turbo-graphql/ for any startup errors across all four services.