GraphQL itself is a query language for your API, but at its core, Apollo Server is an HTTP server that knows how to speak GraphQL.
Let’s see it in action. Imagine we have a simple list of books.
const books = [
{
title: "The Awakening",
author: "Kate Chopin",
},
{
title: "City of Glass",
author: "Paul Auster",
},
];
We define our GraphQL schema using the GraphQL Schema Definition Language (SDL). This schema is the contract between our server and our clients.
type Book {
title: String
author: String
}
type Query {
books: [Book]
}
Now, we need to tell Apollo Server how to resolve the books query. This is where graphql-tools comes in handy, specifically the makeExecutableSchema function. It takes our SDL schema and a set of "resolvers" – functions that fetch the data for each field in our schema.
import { ApolloServer, gql } from 'apollo-server';
import { makeExecutableSchema } from '@graphql-tools/schema';
const typeDefs = gql`
type Book {
title: String
author: String
}
type Query {
books: [Book]
}
`;
const resolvers = {
Query: {
books: () => books,
},
};
const schema = makeExecutableSchema({
typeDefs,
resolvers,
});
const server = new ApolloServer({ schema });
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
When a client sends a query like { books { title } }, Apollo Server receives it. It then looks at the Query type in our schema, finds the books field, and calls the corresponding resolver function. Our resolver simply returns the books array. Apollo Server then takes that data and shapes it according to the client’s request, returning only the title for each book.
The problem this solves is the over-fetching and under-fetching common with REST APIs. With GraphQL, the client exactly specifies the data it needs. If a client only needs the book titles, it queries for { books { title } }, and that’s all it gets. No extra data, no subsequent requests.
Internally, Apollo Server uses graphql-js to parse the incoming GraphQL query, validate it against the schema, and then execute the resolvers. The makeExecutableSchema function from graphql-tools is a convenient wrapper that combines your SDL schema and resolver map into a single executable schema object that graphql-js can understand.
You control the shape of your API through the SDL and the data fetching logic through your resolvers. You can add mutations for creating or updating data, subscriptions for real-time updates, and even directives for custom logic.
The real power of makeExecutableSchema lies in its ability to merge multiple schema definitions and resolver maps. If you have different parts of your application defining different types and queries, you can break them into separate files and then merge them together using makeExecutableSchema with an array of typeDefs and resolvers. This allows for modularity and scalability in larger GraphQL projects.
The next step is to explore how to handle more complex queries, including arguments and nested data.