Episode 8 connects GraphQL to real data sources: data source patterns, SQL integration with Prisma, NoSQL integration with MongoDB and Redis, the RESTDataSource class for external APIs, and best practices for separation of concerns between resolvers and the data layer.

Your GraphQL server is running, but there's no real data yet. Episode 8 changes that: we'll connect GraphQL to databases and external APIs. This is the point where GraphQL truly shines — fetching data from various sources and presenting it through a single endpoint.
We'll cover data source patterns, SQL integration with Prisma, NoSQL integration with MongoDB, using Redis for caching, the RESTDataSource class for consuming external APIs, and best practices for separation of concerns between resolvers and the data layer.
A resolver can fetch data from anywhere. The four most common patterns:
RESTDataSource.The key pattern stays the same: resolvers shouldn't contain direct data logic, but delegate to a separate data layer. This is what's called separation of concerns.
Prisma is a modern TypeScript ORM that generates types from the database schema — a perfect match for GraphQL. Setting it up:
npm install @prisma/client
npm install -D prisma
npx prisma initThen define the models in schema.prisma:
model User {
id Int @id @default(autoincrement())
username String @unique
email String @unique
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
body String
authorId Int
author User @relation(fields: [authorId], references: [id])
}Run npx prisma migrate dev to create the tables, then put the Prisma client into the context:
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
const server = new ApolloServer({
typeDefs,
resolvers,
});
const { url } = await startStandaloneServer(server, {
context: async () => ({ prisma }),
});Resolvers can now use ctx.prisma:
Query: {
post: (_, args, ctx) =>
ctx.prisma.post.findUnique({ where: { id: Number(args.id) } }),
},In production, use connection pooling like PgBouncer, and write selective queries so you don't fetch excessive columns. The N+1 problem that arises from the resolver chain pattern will be solved with DataLoader in episode 9.
For document databases, use MongoDB with Mongoose:
npm install mongooseimport { Schema, model } from "mongoose";
const userSchema = new Schema({
username: { type: String, required: true, unique: true },
email: String,
});
export const User = model("User", userSchema);Redis is very useful as a cache layer between resolvers and the database. A common pattern: check the cache first, and if it's empty, fetch from the database and store it in the cache with a TTL. We'll dissect comprehensive caching strategies in episode 20, including using Redis to store query results.
To consume REST APIs from inside resolvers, Apollo provides the RESTDataSource class. It gives you automatic batching, per-request caching, and request de-duplication, which are very useful:
import { RESTDataSource } from "@apollo/datasource-rest";
export class UsersAPI extends RESTDataSource {
baseURL = "https://api.example.com/";
async getUser(id) {
return this.get(`users/${id}`);
}
async getPostsByUser(userId) {
return this.get("posts", { params: { userId } });
}
}Instantiate this data source and put it in the context. This pattern is the foundation of the REST-to-GraphQL migration strategy in episode 47 — existing REST APIs can be wrapped without being rewritten.
The main principle: keep resolvers thin — they only handle "how the client requests data" — while the data layer handles "where the data comes from". Practical implementation:
import { UserRepository } from "./data/user.repository";
const resolvers = {
Query: {
user: (_, args, ctx) => ctx.repos.users.findById(args.id),
},
};This pattern makes resolvers easy to test (episode 21), easy to swap data sources, and easy to optimize centrally.
Key takeaways:
RESTDataSource wraps external APIs with automatic batching and caching.In the next episode, episode 9, you'll learn about DataLoader and the N+1 problem — what N+1 is, why GraphQL easily triggers it, batching and caching with DataLoader, integrating loaders into the per-request context, and techniques for monitoring performance improvements. You'll conquer GraphQL's classic problem!