GraphQL
Balda provides built-in support for GraphQL through integration with Apollo Server. GraphQL support is optional and requires installing peer dependencies.
Installation
First, install the required peer dependencies:
npm install @apollo/server @graphql-tools/schema graphql
or with yarn:
yarn add @apollo/server @graphql-tools/schema graphql
Basic Setup
Enable GraphQL by passing a graphql configuration object when creating your server:
import { Server } from "balda";
const server = new Server({
port: 3000,
graphql: {
schema: {
typeDefs: `
type Query {
hello: String
}
`,
resolvers: {
Query: {
hello: () => "Hello from GraphQL!",
},
},
},
},
});
await server.listen();
Your GraphQL endpoint will be available at /graphql by default.
Configuration Options
Schema Configuration
The schema option accepts type definitions and resolvers:
const server = new Server({
graphql: {
schema: {
typeDefs: `
type User {
id: ID!
name: String!
email: String!
}
type Query {
users: [User!]!
user(id: ID!): User
}
type Mutation {
createUser(name: String!, email: String!): User!
}
`,
resolvers: {
Query: {
users: () => {
// Fetch users from database
return [];
},
user: (parent, args) => {
// Fetch user by ID
return { id: args.id, name: "John", email: "john@example.com" };
},
},
Mutation: {
createUser: (parent, args) => {
// Create user in database
return { id: "1", name: args.name, email: args.email };
},
},
},
},
},
});
Apollo Server Options
You can pass additional options to Apollo Server:
const server = new Server({
graphql: {
schema: {
typeDefs: `...`,
resolvers: {},
},
apolloOptions: {
introspection: true,
includeStacktraceInErrorResponses: false,
csrfPrevention: true,
cache: "bounded",
plugins: [
// Add your Apollo Server plugins here
],
},
},
});
For a full list of Apollo Server options, see the Apollo Server documentation.
Dynamic Schema Building
Balda provides methods to add type definitions and resolvers dynamically:
Adding Type Definitions
Use addTypeDef() to add type definitions after server initialization:
const server = new Server({ graphql: {} });
// Add type definitions
server.graphql.addTypeDef(`
type Book {
id: ID!
title: String!
author: String!
}
`);
server.graphql.addTypeDef(`
extend type Query {
books: [Book!]!
book(id: ID!): Book
}
`);
You can also add multiple type definitions at once:
server.graphql.addTypeDef([
`type Author { id: ID!, name: String! }`,
`type Publisher { id: ID!, name: String! }`,
]);
Adding Resolvers
Use addResolver() to add resolvers dynamically:
// Add resolvers for a specific type
server.graphql.addResolver("Query", {
books: () => {
return [{ id: "1", title: "GraphQL Basics", author: "John Doe" }];
},
book: (parent, args) => {
return { id: args.id, title: "Book Title", author: "Author" };
},
});
server.graphql.addResolver("Mutation", {
createBook: (parent, args) => {
return { id: "1", title: args.title, author: args.author };
},
});
You can also add full resolver objects:
server.graphql.addResolver({
Query: {
users: () => [],
},
Mutation: {
createUser: (parent, args) => args,
},
});
For custom types:
server.graphql.addResolver("Book", {
author: (parent) => {
// Resolve nested author field
return { id: "1", name: parent.author };
},
});
Typed Context
Balda exports a GraphQLContext interface that you can extend to provide type-safe context in your resolvers.
Extending the Context
Use TypeScript's module augmentation to extend the context type:
import type { GraphQLContext } from "balda";
// Define your custom context properties
interface User {
id: string;
name: string;
email: string;
}
interface Database {
users: Map<string, User>;
}
// Extend the GraphQLContext interface
declare module "balda" {
interface GraphQLContext {
user?: User;
db: Database;
req: Request;
}
}
Using Typed Context in Resolvers
Once extended, your resolvers will have full type safety:
server.graphql.addResolver("Query", {
me: (parent, args, context) => {
// context.user is now fully typed!
if (!context.user) {
throw new Error("Not authenticated");
}
return context.user;
},
users: (parent, args, context) => {
// context.db is typed as Database
return Array.from(context.db.users.values());
},
});
Providing Context
Context values are automatically provided by Apollo Server. The default context includes the Balda request object. You can extend the context using Apollo Server's context function:
const db: Database = {
users: new Map(),
};
const server = new Server({
graphql: {
schema: {
typeDefs: `...`,
resolvers: {},
},
apolloOptions: {
context: async ({ req }) => {
// Extract user from request headers
const token =
req.headers?.get?.("authorization") ?? req.headers?.authorization;
const user = token ? await verifyToken(token) : undefined;
return {
user,
db,
req,
};
},
},
},
});
Resolver Function Signature
All resolver functions follow the standard GraphQL signature:
type GraphQLResolverFunction<TContext = GraphQLContext> = (
parent: unknown,
args: Record<string, unknown>,
context: TContext,
info: GraphQLResolveInfo,
) => unknown | Promise<unknown>;
- parent: The result of the parent resolver (for nested fields)
- args: The arguments provided to the field
- context: The shared context object (typed via module augmentation)
- info: GraphQL execution info (field name, parent type, schema details, etc.)
Resolvers can return values synchronously or return a Promise for async operations.
Error Handling
Apollo Server automatically catches errors thrown in resolvers:
server.graphql.addResolver("Query", {
user: async (parent, args, context) => {
const user = await context.db.users.get(args.id);
if (!user) {
throw new Error("User not found");
}
return user;
},
});
For custom error handling, use Apollo Server's error formatting options:
import { GraphQLError } from "graphql";
const server = new Server({
graphql: {
apolloOptions: {
formatError: (formattedError, error) => {
// Don't expose internal errors in production
if (formattedError.message.includes("INTERNAL")) {
return new GraphQLError("Internal server error", {
extensions: {
code: "INTERNAL_SERVER_ERROR",
},
});
}
return formattedError;
},
},
},
});
Apollo Sandbox
Apollo Server includes a built-in Apollo Sandbox for GraphQL exploration and testing. Access it by navigating to your GraphQL endpoint in a browser:
http://localhost:3000/graphql
Enabling Apollo Sandbox
To enable the Apollo Sandbox UI with full introspection support, configure your server with the landing page plugin:
import { ApolloServerPluginLandingPageLocalDefault } from "@apollo/server/plugin/landingPage/default";
import { Server } from "balda";
const server = new Server({
graphql: {
schema: {
typeDefs: `...`,
resolvers: {},
},
apolloOptions: {
introspection: true, // Required for schema introspection
csrfPrevention: false, // Disable for local development
plugins: [ApolloServerPluginLandingPageLocalDefault()],
},
},
});
Important configuration options:
introspection: true- Enables schema introspection, allowing the Sandbox UI to load your GraphQL schemacsrfPrevention: false- Disables CSRF prevention for local development (recommended for development only)ApolloServerPluginLandingPageLocalDefault()- Provides the Apollo Sandbox UI
Using the Sandbox UI
Once your server is running, navigate to http://localhost:3000/graphql in your browser. The Sandbox UI provides:
1. Interactive Query Editor
- Auto-completion for types, fields, and arguments
- Syntax highlighting
- Real-time validation
- Multiple tabs for different operations
2. Schema Explorer
- Browse your entire GraphQL schema
- View type definitions and relationships
- Search for specific types and fields
- Documentation for each field (from schema descriptions)
3. Query Variables
- Test queries with dynamic variables
- JSON editor with syntax validation
- Variable type checking
4. Request Headers
- Add custom HTTP headers (authentication, etc.)
- Test authorization and authentication flows
5. Operation History
- Access previously executed queries
- Save frequently used operations
6. Response Viewer
- Formatted JSON responses
- Error details with stack traces (in development)
- Response timing information
Example: Testing with Sandbox UI
import { ApolloServerPluginLandingPageLocalDefault } from "@apollo/server/plugin/landingPage/default";
import { Server } from "balda";
const server = new Server({
port: 3000,
graphql: {
schema: {
typeDefs: `
type User {
id: ID!
name: String!
email: String!
}
type Query {
user(id: ID!): User
users: [User!]!
}
type Mutation {
createUser(name: String!, email: String!): User!
}
`,
resolvers: {
Query: {
user: (parent, args) => ({
id: args.id,
name: "John Doe",
email: "john@example.com",
}),
users: () => [
{ id: "1", name: "John Doe", email: "john@example.com" },
{ id: "2", name: "Jane Smith", email: "jane@example.com" },
],
},
Mutation: {
createUser: (parent, args) => ({
id: "3",
name: args.name,
email: args.email,
}),
},
},
},
apolloOptions: {
introspection: true,
csrfPrevention: false,
plugins: [ApolloServerPluginLandingPageLocalDefault()],
},
},
});
server.listen(({ url }) => {
console.log(`Server running at ${url}`);
console.log(`Apollo Sandbox: ${url}/graphql`);
});
After starting the server, open http://localhost:3000/graphql and try these queries in the Sandbox:
Query with arguments:
query GetUser($userId: ID!) {
user(id: $userId) {
id
name
email
}
}
Variables:
{
"userId": "1"
}
Mutation:
mutation CreateUser($name: String!, $email: String!) {
createUser(name: $name, email: $email) {
id
name
email
}
}
Production Configuration
For production environments, disable introspection and use a different landing page:
const server = new Server({
graphql: {
apolloOptions: {
// Disable introspection in production
introspection: process.env.NODE_ENV !== "production",
// Enable CSRF prevention
csrfPrevention: true,
// Use production landing page
plugins:
process.env.NODE_ENV === "production"
? [ApolloServerPluginLandingPageDisabled()]
: [ApolloServerPluginLandingPageLocalDefault()],
},
},
});
Troubleshooting Sandbox Issues
If you see "Schema Introspection Failure" in the Sandbox:
-
Ensure introspection is enabled:
apolloOptions: {
introspection: true,
} -
Disable CSRF prevention for local development:
apolloOptions: {
csrfPrevention: false,
} -
Check that the GraphQL endpoint is accessible:
- Open the browser console
- Look for network errors
- Verify the server is running
-
Test with a simple curl command:
curl -X POST http://localhost:3000/graphql \
-H 'Content-Type: application/json' \
-d '{"query":"{ __typename }"}'
Alternative: Disable Landing Page
If you don't want the Sandbox UI, you can disable it:
import { ApolloServerPluginLandingPageDisabled } from "@apollo/server/plugin/disabled";
const server = new Server({
graphql: {
apolloOptions: {
plugins: [ApolloServerPluginLandingPageDisabled()],
},
},
});
Lazy Loading
GraphQL dependencies are loaded lazily when the first GraphQL request is made. This means:
- If GraphQL is not configured, the dependencies are never loaded
- Apollo Server is only started when needed, improving startup performance
If the GraphQL peer dependencies are not installed and GraphQL is enabled, you'll receive a clear error message:
GraphQL is enabled but '@apollo/server' is not installed.
Install it with: npm install @apollo/server @graphql-tools/schema graphql
Runtime Support
GraphQL support works across all Balda runtimes:
- ✅ Node.js
- ✅ Bun
- ✅ Deno
The implementation uses dynamic imports to load Apollo Server only when needed, ensuring optimal performance across all platforms.
Advanced Features
Plugins
Apollo Server supports a powerful plugin system. You can add plugins for logging, caching, metrics, and more:
const server = new Server({
graphql: {
apolloOptions: {
plugins: [
{
async requestDidStart() {
return {
async willSendResponse({ response }) {
console.log("Sending response:", response);
},
};
},
},
],
},
},
});
Subscriptions
For GraphQL subscriptions support, you can configure Apollo Server with the appropriate transport:
import { WebSocketServer } from "ws";
import { useServer } from "graphql-ws/lib/use/ws";
const server = new Server({
graphql: {
schema: {
typeDefs: `
type Subscription {
messageAdded: Message
}
`,
resolvers: {
Subscription: {
messageAdded: {
subscribe: () => pubsub.asyncIterator(["MESSAGE_ADDED"]),
},
},
},
},
},
});
Next Steps
- Explore Apollo Server documentation
- Learn about GraphQL schema design
- Check out Apollo Server plugins
- Read about GraphQL best practices