Build a GraphQL REST Gateway
Build a GraphQL gateway over REST with nested queries and mutations.
Introduction
30 Second Summary
A single screen often pulls information from several places. With a familiar REST API, the client may need separate requests before it can assemble the view.
In this project, you will build a small GraphQL gateway over JSONPlaceholder. The finished API lets a client request one post with its comments in a precise response shape.
What You'll Build
In Apollo Sandbox, one operation returns a post with five comments in exactly the shape you requested.
By the end of this project, you'll have:
- A visible REST baseline that proves one post-with-comments view needs two endpoint requests.
- A nested GraphQL query whose response contains one post with five comments in the exact selection shape.
- A structured mutation that creates a sample post through the upstream REST API. An introspection query that displays the fields in the Post type.
- Secret Mission: Add a filtered list query that returns posts for one user.
Are there any prerequisites?
You should know basic JavaScript plus asynchronous functions. You should be comfortable reading JSON responses from REST APIs over HTTP.
You also need Visual Studio Code plus Node.js version 20 or newer on Windows.
Before We Start
Before any hands-on work, take a moment to lock in what you're building and why it matters. You are building a local GraphQL layer over JSONPlaceholder REST so a client can request a precise nested data shape.
Set Up and Feel the REST Limitation
A GraphQL gateway becomes useful when it removes client-side data stitching. The familiar REST version of a post-with-comments view requires two endpoint requests.
This step creates that baseline against JSONPlaceholder. You will see the limitation in your own terminal before building the GraphQL alternative.
In this step, get ready to:
- Verify Node.js 20 or newer with npm in the Visual Studio Code PowerShell terminal.
- Initialize graphql-rest-gateway with Apollo Server 5.5.1 plus GraphQL.js 16.11.0.
- Run the REST baseline to observe two requests for one combined view.
Verify Node.js and npm
Apollo Server needs a compatible Node.js runtime before it can start. The version checks confirm that your Windows environment is ready for the pinned dependencies.
- Press the Windows key to open Windows search.
- Type Visual Studio Code into the search field.
- Press Enter to start Visual Studio Code.
- Click View in the top menu.
- Click Terminal to open the integrated terminal.
- Open the terminal profile dropdown in the terminal panel.
- Select PowerShell as the terminal profile.
- Check your installed Node.js version by running:
node --version
What does this command do?
The command prints the installed Node.js version. Apollo Server can use the runtime when the major version is 20 or higher.
✔️ I see version 20 or higher
Your Node.js version meets the requirement. Continue below to verify npm.
ⓧ I see an older version
Your installed version is below the project requirement. Upgrade to the current LTS release before continuing.
- Visit the official Node.js download page.
- Download the Windows installer for the current v24.21.0 LTS release.
- Run the downloaded installer with its default options.
- Close Visual Studio Code after the installation completes.
- Press the Windows key to reopen Windows search.
- Type Visual Studio Code into the search field.
- Press Enter to reopen Visual Studio Code.
- Open a new integrated terminal from the Terminal menu.
- Repeat the Node.js version check by running:
node --version
What should I see?
The refreshed terminal should print Node.js v24.21.0 or another version above 20.
ⓧ Command not found
Windows cannot currently find Node.js. The official LTS installer adds Node.js plus npm to your system.
- Visit the official Node.js download page.
- Download the Windows installer for the current v24.21.0 LTS release.
- Run the downloaded installer with its default options.
- Close Visual Studio Code after the installation completes.
- Press the Windows key to reopen Windows search.
- Type Visual Studio Code into the search field.
- Press Enter to reopen Visual Studio Code.
- Open a new integrated terminal from the Terminal menu.
- Confirm that Windows can now find Node.js by running:
node --version
What should I see?
The terminal should print Node.js v24.21.0 or another version above 20.
- Verify that npm is available by running:
npm --version
What does this command do?
The command prints the installed npm version. A version number confirms that npm is available for initializing the project and installing packages.
Your JavaScript toolchain is ready. Node.js can run the project while npm manages its dependencies.
Create and initialize the project
A package manifest records how the project starts plus which libraries it needs. Exact dependency versions keep your local setup aligned with the code in this project.
- Move the PowerShell terminal to your Desktop by running:
Set-Location -Path "$Env:USERPROFILE\Desktop"
What does this command do?
PowerShell changes its current location to your Windows Desktop. The project folder you create next will be easy to find.
- Create the graphql-rest-gateway folder by running:
New-Item -Path . -Name "graphql-rest-gateway" -ItemType Directory
What does this command do?
PowerShell creates a directory named graphql-rest-gateway on your Desktop. The returned directory details confirm that the folder exists.
- Move the terminal into graphql-rest-gateway by running:
Set-Location -Path ".\graphql-rest-gateway"
What does this command do?
PowerShell makes graphql-rest-gateway the current location. Every project command now runs inside that folder.
- Click File in the Visual Studio Code menu.
- Click Open Folder....
- Select the graphql-rest-gateway folder from your Desktop.
- Choose Yes, I trust the authors if the Workspace Trust dialog appears.
- Click Terminal in the top menu.
- Click New Terminal to start a terminal inside graphql-rest-gateway.
The terminal prompt should end with graphql-rest-gateway. This confirms that npm will create its files in the correct folder.
- Initialize the npm project by running:
npm init --yes
What does this command do?
npm creates package.json with default project metadata. This file becomes the central record for scripts and dependencies.
- Configure module support plus the project scripts by running:
npm pkg set type=module scripts.start="node index.js" scripts.rest="node rest-demo.js"
What does this command do?
The command updates package.json with module support. It also adds the start and rest scripts used throughout the project.
The dependency installation may take a minute while npm downloads each package. Progress messages in the terminal mean the installation is still working.
- Install the exact Apollo Server and GraphQL.js versions by running:
npm install @apollo/server@5.5.1 graphql@16.11.0 --save-exact
What does this command do?
npm installs the packages inside node_modules. The --save-exact flag records the selected versions without a range operator.
Dependencies not installing?
Confirm that the terminal prompt ends with graphql-rest-gateway. A network or package registry issue can also interrupt the download.
Retry the installation after confirming your internet connection. Help me diagnose an npm dependency installation failure on Windows.
- Select package.json in the Explorer sidebar.
- Replace the empty description value with A beginner GraphQL API that wraps JSONPlaceholder REST endpoints..
- Save package.json by pressing Ctrl+S.
- Confirm the generated project files by running:
Get-ChildItem
What does this command do?
PowerShell lists the contents of graphql-rest-gateway. This gives you a visible check that npm created the expected local artifacts.
You should see node_modules, package-lock.json and package.json in the output. Your local project now has its pinned dependencies.
Build the REST baseline
JSONPlaceholder exposes the post and its comments through separate routes. The baseline script counts each upstream request before printing one combined summary.
- Hover over graphql-rest-gateway in the Explorer sidebar.
- Click the new file icon in the Explorer toolbar.
- Enter rest-demo.js as the file name.
- Press Enter to create the file.
- Add the REST request counter plus the first post request by pasting this code into rest-demo.js:
const REST_BASE_URL = 'https://jsonplaceholder.typicode.com';
let requestCount = 0;
async function fetchJson(path) {
requestCount += 1;
console.log(`REST request ${requestCount}: GET ${path}`);
const response = await fetch(`${REST_BASE_URL}${path}`);
if (!response.ok) {
throw new Error('The REST request failed.');
}
return response.json();
}
const post = await fetchJson('/posts/1');
What does this code do?
- REST_BASE_URL stores the shared JSONPlaceholder address.
- requestCount tracks how many upstream REST requests the script makes.
- fetchJson() logs each request before using fetch() to retrieve JSON data.
- post holds the response from /posts/1.
- Save rest-demo.js by pressing Ctrl+S.
- Test the first REST request by running:
npm run rest
What does this command do?
npm runs the rest script from package.json. The current script requests only the post resource.
You should see REST request 1: GET /posts/1 in the terminal. This proves the first REST route is reachable.
First request not completing?
Confirm that rest-demo.js is saved inside graphql-rest-gateway. A network restriction can also prevent the request from reaching JSONPlaceholder.
Compare the first code chunk with your file. Help me debug the first JSONPlaceholder request in rest-demo.js.
- Find this line in rest-demo.js: const post = await fetchJson('/posts/1');
- Add the comments request plus the combined summary directly below that line:
const comments = await fetchJson('/posts/1/comments');
console.log({
post: {
id: post.id,
title: post.title,
},
commentCount: comments.length,
restRequests: requestCount,
});
What does this code do?
- comments holds the response from /posts/1/comments.
- console.log() combines the post title with the comment count in one local summary.
- restRequests exposes the final request count so the REST limitation is visible.
- Save rest-demo.js by pressing Ctrl+S.
✔️ Awesome, I've got everything!
Your project metadata and REST baseline are ready. Keep the PowerShell terminal open in graphql-rest-gateway.
ⓧ I'd like to double check the full code
Compare your complete package.json with this reference:
{
"name": "graphql-rest-gateway",
"version": "1.0.0",
"description": "A beginner GraphQL API that wraps JSONPlaceholder REST endpoints.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node index.js",
"rest": "node rest-demo.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "module",
"dependencies": {
"@apollo/server": "5.5.1",
"graphql": "16.11.0"
}
}
What should this file contain?
This reference includes the exact startup scripts plus the pinned Apollo Server and GraphQL.js dependencies. Your saved package.json should match every field shown here.
Compare your complete rest-demo.js with this reference:
const REST_BASE_URL = 'https://jsonplaceholder.typicode.com';
let requestCount = 0;
async function fetchJson(path) {
requestCount += 1;
console.log(`REST request ${requestCount}: GET ${path}`);
const response = await fetch(`${REST_BASE_URL}${path}`);
if (!response.ok) {
throw new Error('The REST request failed.');
}
return response.json();
}
const post = await fetchJson('/posts/1');
const comments = await fetchJson('/posts/1/comments');
console.log({
post: {
id: post.id,
title: post.title,
},
commentCount: comments.length,
restRequests: requestCount,
});
What should this file contain?
This reference makes one request for the post plus one request for its comments. It then prints the combined result with the total request count.
Before you run the finished baseline, how many REST requests do you expect one post-with-comments summary to require?
- Run the completed REST baseline by running:
npm run rest
What should I see?
- The terminal logs one GET request for /posts/1.
- The terminal logs a second GET request for /posts/1/comments.
- The final summary contains one post title.
- The final summary shows commentCount: 5 plus restRequests: 2.
You found the limitation this project will solve. One client view needs two separate REST requests before it can display the post with its comments.
Missing one of the two requests?
Confirm that both fetchJson() calls appear after the function. Check that the comments path ends with /posts/1/comments.
Use the full-code tab to check every bracket plus comma. Help me debug why my REST baseline does not report two requests.
Your local project is ready, and the two-request baseline is visible. Next, you will replace that client-side stitching with your first typed GraphQL query.
Build Your First GraphQL Query
Your REST baseline proved the client needs two endpoint requests to assemble one post view. The data arrives, but the client still has no queryable contract for choosing its response shape.
In this step, you'll build a GraphQL schema that exposes selected fields from a post. The first contract contains the post fields only, which lets you test how the schema handles a request for comments.
In this step, get ready to:
- Define a typed post query over JSONPlaceholder.
- Connect the query through a resolver map.
- Test the schema contract in Apollo Sandbox.
Lay the gateway foundation
An Apollo Server gateway receives GraphQL operations from the client. Its resolver can load the requested data from JSONPlaceholder through the existing REST API.
- In the Explorer sidebar in VS Code, hover over the graphql-rest-gateway folder name.
- Click the new file icon.
You'll see a field for the new file name.
- Type index.js into the field.
- Press Enter to create the file.
The Explorer sidebar now lists index.js inside graphql-rest-gateway.
- Add the Apollo imports and REST request helper by pasting this code at the top of index.js:
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
const REST_BASE_URL = 'https://jsonplaceholder.typicode.com';
async function requestRest(path, options = {}) {
const method = options.method ?? 'GET';
console.log(`Upstream REST request: ${method} ${path}`);
const response = await fetch(`${REST_BASE_URL}${path}`, options);
if (!response.ok) {
throw new Error('The upstream REST request failed.');
}
return response.json();
}
What Does This Code Do?
- The first import makes ApolloServer available to your file.
- The second import provides the standalone server launcher.
- REST_BASE_URL stores the shared address for every upstream request.
- requestRest() records each upstream request in the terminal.
- fetch() sends the request to JSONPlaceholder.
- response.json() converts the successful response into data that a resolver can return.
- Save index.js by pressing Cmd+S (macOS) or Ctrl+S (Windows).
- Confirm that index.js remains visible in the Explorer sidebar.
Code in the Wrong File?
Check that the active editor tab says index.js. Move the pasted code into that file if another tab was active.
Confirm that index.js sits directly inside graphql-rest-gateway.
Help me check the location and first section of my index.js file.
The REST helper supplies data to the gateway. The schema now needs to define the fields that a client is allowed to request.
- Add the initial type definitions directly below requestRest() in index.js by pasting:
const typeDefs = `#graphql
"A post loaded from the REST API."
type Post {
id: Int!
userId: Int!
title: String!
body: String!
}
type Query {
post(id: Int!): Post
}
`;
How Does the Schema Work?
- typeDefs holds the API contract in GraphQL's schema definition language.
- Post describes the four fields available on a post.
- Int! declares a required integer value.
- String! declares a required text value.
- Query provides the entry point for reading data.
- post(id: Int!): Post accepts a required post ID and returns a post when one exists.
- Save index.js.
- Confirm that the file now contains the Post and Query type definitions.
Schema Text Looking Broken?
Check that the schema starts with the backtick after #graphql. Confirm that the closing backtick appears before the final semicolon.
Compare every brace around Post and Query.
Help me find a formatting problem in my GraphQL typeDefs string.
Connect and launch the server
A resolver map connects schema fields to the code that supplies their values. The post resolver receives the client's id argument and turns it into a REST path.
- Add the resolver map directly below typeDefs in index.js by pasting:
const resolvers = {
Query: {
post: (_, { id }) => requestRest(`/posts/${id}`),
},
};
How Does the Resolver Work?
- The Query key matches the root type in the schema.
- The post key matches the field exposed by that root type.
- { id } extracts the argument supplied by the client.
- requestRest(`/posts/${id}`) loads the matching post from the upstream REST resource.
- Save index.js.
- Confirm that the Query key contains a post resolver.
Resolver Names Not Matching?
Check that both the schema and resolver map use the field name post. GraphQL connects those sections through the matching name.
Help me compare my Query field with my resolver map.
The schema and resolver map are connected. The final section creates the server instance and listens for local requests.
- Add the server startup section directly below resolvers in index.js by pasting:
const server = new ApolloServer({
typeDefs,
resolvers,
});
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
});
console.log(`GraphQL server ready at ${url}`);
What Starts the Server?
- new ApolloServer() combines the schema with its resolver map.
- startStandaloneServer() starts the local GraphQL service.
- listen: { port: 4000 } assigns the local port used by Apollo Sandbox.
- url holds the address printed after startup succeeds.
- Save index.js.
- Confirm that the final line logs the server URL.
Startup Section in the Wrong Place?
Confirm that the server startup section appears after the closing brace of resolvers. Code placed inside the resolver map prevents the file from loading.
Help me check the order of the sections in index.js.
✔️ Awesome, I've got everything!
Your complete index.js file is saved and ready to run.
ⓧ I'd like to double check the full code
Compare your index.js file with this complete version:
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
const REST_BASE_URL = 'https://jsonplaceholder.typicode.com';
async function requestRest(path, options = {}) {
const method = options.method ?? 'GET';
console.log(`Upstream REST request: ${method} ${path}`);
const response = await fetch(`${REST_BASE_URL}${path}`, options);
if (!response.ok) {
throw new Error('The upstream REST request failed.');
}
return response.json();
}
const typeDefs = `#graphql
"A post loaded from the REST API."
type Post {
id: Int!
userId: Int!
title: String!
body: String!
}
type Query {
post(id: Int!): Post
}
`;
const resolvers = {
Query: {
post: (_, { id }) => requestRest(`/posts/${id}`),
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
});
console.log(`GraphQL server ready at ${url}`);
Before you start the server, predict what the terminal will show when the schema is ready.
- Start the GraphQL server in the PowerShell terminal from earlier by running:
npm start
What Does This Command Do?
The start script runs index.js through Node.js. The terminal stays active while the local server listens for requests.
You'll see a message confirming that the GraphQL server is ready at http://localhost:4000/.
- Open http://localhost:4000/ in your web browser.
You'll see Apollo Sandbox connected to your local API. Your first GraphQL service is now live and ready to accept an operation.
Server Not Starting?
Confirm that the PowerShell terminal is still inside graphql-rest-gateway. The start script depends on the package.json in that folder.
Compare index.js with the complete version above if the terminal reports a syntax problem.
Help me debug why my local Apollo Server does not start.
Test the schema contract
A selection set lists the fields you want back from GraphQL. A variable supplies a changing input separately from the operation text.
- Paste this named operation into the Apollo Sandbox operation editor:
query GetPost($id: Int!) {
post(id: $id) {
id
title
}
}
What Does This Operation Request?
- GetPost gives the operation a meaningful name.
- $id declares a variable that must contain an integer.
- post(id: $id) passes that variable into the root query field.
- The nested selection requests only id and title from the returned post.
- Paste this dictionary into the Apollo Sandbox variables panel:
{
"id": 1
}
How Does the Variable Reach the Query?
The JSON dictionary assigns 1 to the operation's id variable. Apollo Server passes that value to the post resolver.
Before you execute GetPost, predict which post fields the response will contain.
- Execute GetPost in Apollo Sandbox.
Apollo Sandbox shows a response containing only id and title. Your PowerShell terminal logs one upstream REST request to /posts/1.
That's your first GraphQL query working. The client selected its response shape while the resolver handled the REST request.
The successful operation stayed within the current schema contract. Now you can ask the same contract for a nested field.
- Add this selection directly below title inside the post field:
comments {
id
email
}
What Does This Selection Ask For?
The new selection asks GraphQL to traverse from a post to its comments. It requests only the comment id and email fields.
Before you execute the edited operation, predict how the schema will respond to a field outside its current contract.
- Execute GetPost again in Apollo Sandbox.
Apollo Sandbox shows validation feedback because comments is not defined on Post.
Why This Validation Matters
GraphQL validates an operation against the schema before running its resolvers. The schema protects the API contract by rejecting fields it does not expose.
Your server now returns client-selected post fields and rejects requests outside its contract. Next, you'll add the missing relationship so one operation can retrieve a post with its comments.
Join REST Data Through the Graph
Your Apollo Server can already return selected fields from one REST post. Apollo Sandbox also showed that the current schema rejects a nested comments selection.
Now you'll add a nested field resolver that uses the post result as its parent value. One GraphQL operation can then shape a post plus its five comments.
In this step, get ready to:
- Define a Comment type with a comments field on Post.
- Connect Post.comments to the JSONPlaceholder comments endpoint.
- Verify GetPostAndComments returns five selected comments.
Define the comment relationship
The schema must describe a comment before clients can select its fields. The comments: [Comment!]! field connects each Post to a non-null list of comments.
- Switch back to the index.js editor tab from the previous step.
- Find the existing const typeDefs declaration.
- Replace that declaration with the schema below:
const typeDefs = `#graphql
"A comment attached to a post."
type Comment {
id: Int!
postId: Int!
name: String!
email: String!
body: String!
}
"A post loaded from the REST API."
type Post {
id: Int!
userId: Int!
title: String!
body: String!
comments: [Comment!]!
}
type Query {
post(id: Int!): Post
}
`;
What does this schema add?
- The Comment type describes the fields returned by the comments REST endpoint.
- The comments: [Comment!]! field makes comments selectable beneath a post.
- The square brackets define a list. The exclamation marks require the list plus each item to contain a value.
- Save index.js.
- Use the editor's search box to find comments: [Comment!]!.
You should find the field inside Post. The Comment type should appear directly above it.
Schema structure looking wrong?
Check that Comment remains inside the typeDefs template string. Confirm that comments appears before the closing brace of Post.
Compare the opening braces with their closing braces. A missing brace can prevent Apollo Server from reading the schema.
Help me check my GraphQL type definitions.
Resolve comments from the parent post
A field in the schema establishes the contract. A resolver supplies data when a client selects that field.
- Find the existing const resolvers declaration in index.js.
- Replace that declaration with the resolver map below:
const resolvers = {
Query: {
post: (_, { id }) => requestRest(`/posts/${id}`),
},
Post: {
comments: (post) => requestRest(`/posts/${post.id}/comments`),
},
};
How does the resolver chain work?
- The Query.post resolver returns the post loaded from /posts/${id}.
- Apollo passes that returned post into Post.comments as the post parent value.
- The nested resolver reads post.id to request the matching comments.
- This chain lets the client request related data through one nested operation.
- Save index.js.
- Compare your completed file with the reference below if you want a final code check.
✔️ Awesome, I've got everything!
Your index.js now contains the Comment type plus the Post.comments resolver.
ⓧ I'd like to double check the full code
Compare your complete index.js file with this reference:
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
const REST_BASE_URL = 'https://jsonplaceholder.typicode.com';
async function requestRest(path, options = {}) {
const method = options.method ?? 'GET';
console.log(`Upstream REST request: ${method} ${path}`);
const response = await fetch(`${REST_BASE_URL}${path}`, options);
if (!response.ok) {
throw new Error('The upstream REST request failed.');
}
return response.json();
}
const typeDefs = `#graphql
"A comment attached to a post."
type Comment {
id: Int!
postId: Int!
name: String!
email: String!
body: String!
}
"A post loaded from the REST API."
type Post {
id: Int!
userId: Int!
title: String!
body: String!
comments: [Comment!]!
}
type Query {
post(id: Int!): Post
}
`;
const resolvers = {
Query: {
post: (_, { id }) => requestRest(`/posts/${id}`),
},
Post: {
comments: (post) => requestRest(`/posts/${post.id}/comments`),
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
});
console.log(`GraphQL server ready at ${url}`);
What should match?
This reference preserves the existing server code. It adds only the comment type, the nested field, plus its resolver.
Resolver map not matching?
Make sure Post sits beside Query inside resolvers. The comments function must remain inside Post.
Confirm that the endpoint uses post.id. The parent object holds the post identifier needed by the comments request.
Help me debug my nested comments resolver.
Run the nested query
The running server still holds the previous schema in memory. Restarting it loads the new relationship before Apollo Sandbox sends the nested operation.
- Switch back to the VS Code PowerShell terminal from earlier.
- Stop the running server by pressing Ctrl+C.
- Restart the updated server by running:
npm start
What does this command do?
The npm start command runs node index.js from the scripts.start property in package.json. This starts the updated server on port 4000.
You should see GraphQL server ready at http://localhost:4000/ in the terminal. Your nested relationship is now live.
Server not restarting?
If the server reports a schema problem, compare the braces in typeDefs with the full code above. Check the commas around the Post resolver if JavaScript reports a syntax problem.
Help me debug why the updated GraphQL server will not start.
- Return to the Apollo Sandbox page from the previous step.
- Replace the current operation with this named query:
query GetPostAndComments($id: Int!) {
post(id: $id) {
id
title
body
comments {
id
email
}
}
}
How does this operation work?
- The name GetPostAndComments identifies this operation in logs and debugging tools.
- The $id variable supplies the post identifier without placing a changing value inside the operation.
- The nested selection asks for three post fields plus two fields from each comment.
- Replace the variables dictionary with this value:
{
"id": 1
}
What does this variable control?
The variables dictionary assigns 1 to $id. Apollo passes that value to the post(id: $id) field.
Before you execute the operation, how many comment objects do you expect the response to contain?
- Click the triangular run control above the operation editor.
You should see the post's id, title, plus body. The comments array should contain five objects with only id plus email.
You have the join working. One GraphQL operation now returns the post plus its related comments.
- Return to the VS Code PowerShell terminal.
You should see Upstream REST request: GET /posts/1 followed by Upstream REST request: GET /posts/1/comments. Those two lines show how the gateway assembled one client-shaped result.
- Return to Apollo Sandbox.
- Delete body from the post selection.
- Run the operation again with the same triangular control.
The response should now omit body. The five comments should remain because the selection set still requests them.
Comments missing from the response?
Confirm that the query nests comments inside post. Check that Post.comments calls the endpoint with post.id.
If Apollo Sandbox still shows the previous schema, stop the server with Ctrl+C. Run npm start again before repeating the operation.
Help me debug why GetPostAndComments is not returning comments.
Your gateway now turns two REST resources into one client-shaped result. Next, you'll add a mutation that writes through the gateway.
Add a Mutation and Inspect the Schema
Your GraphQL gateway now combines a post with its comments from two REST resources. The client receives that nested result through one operation.
The API still supports read operations only. A purpose-specific mutation adds the write path.
Introspection lets the schema describe its own contract. You will use it to inspect the Post type from Apollo Sandbox.
In this step, get ready to:
- Extend the schema with a structured createPost mutation.
- Connect createPost to JSONPlaceholder through a resolver.
- Inspect the Post contract with an introspection query.
Define the mutation contract
An input object groups the values required for one write operation. The root Mutation type exposes that operation to clients.
- Switch back to the index.js tab in VS Code.
- Stop the running server by pressing Ctrl+C in PowerShell.
- Find the closing brace for type Post inside typeDefs.
- Define the structured mutation input by inserting this block immediately below type Post:
input CreatePostInput {
title: String!
body: String!
userId: Int!
}
What does this input type do?
- CreatePostInput groups the title, body, and user ID into one structured value.
- Each ! makes its field required when a client sends the mutation.
- Find the closing brace for type Query inside typeDefs.
- Define the root write field by inserting this block immediately below type Query:
type Mutation {
createPost(input: CreatePostInput!): Post!
}
What does this mutation type do?
- Mutation becomes the root type for write operations.
- createPost requires one CreatePostInput value.
- Post! promises that a successful operation returns a post.
- Save index.js by pressing Ctrl+S.
- Start the server with the updated schema by running:
npm start
What does this command do?
npm start runs the start script from package.json. That script reloads index.js with the new schema types.
You should see GraphQL server ready at http://localhost:4000/ in PowerShell.
That restart confirms your write contract loads successfully.
Schema not loading?
- Check that CreatePostInput sits outside the closing brace for type Post.
- Check that type Mutation sits inside the typeDefs template string.
- Compare every brace in the two new types with the code blocks above.
Help me find the schema problem in my new input and mutation types.
- Stop the server by pressing Ctrl+C in PowerShell.
Connect the mutation resolver
The schema declares what clients can request. The resolver map performs the write by forwarding the input to the existing REST API.
- Find the Post resolver block inside resolvers.
- Add the mutation resolver immediately below the Post resolver block:
Mutation: {
createPost: (_, { input }) =>
requestRest('/posts', {
method: 'POST',
body: JSON.stringify(input),
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
}),
},
What does this resolver do?
- input comes from the resolver's second argument.
- requestRest forwards the request to /posts on JSONPlaceholder.
- JSON.stringify(input) converts the structured input into a JSON request body.
- Content-type tells the REST API how to interpret that body.
- Save index.js by pressing Ctrl+S.
✔️ Awesome, I've got everything!
Your index.js now contains the complete query path and mutation path.
ⓧ I'd like to double check the full code
- Compare your saved index.js with this complete file:
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
const REST_BASE_URL = 'https://jsonplaceholder.typicode.com';
async function requestRest(path, options = {}) {
const method = options.method ?? 'GET';
console.log(`Upstream REST request: ${method} ${path}`);
const response = await fetch(`${REST_BASE_URL}${path}`, options);
if (!response.ok) {
throw new Error('The upstream REST request failed.');
}
return response.json();
}
const typeDefs = `#graphql
"A comment attached to a post."
type Comment {
id: Int!
postId: Int!
name: String!
email: String!
body: String!
}
"A post loaded from the REST API."
type Post {
id: Int!
userId: Int!
title: String!
body: String!
comments: [Comment!]!
}
input CreatePostInput {
title: String!
body: String!
userId: Int!
}
type Query {
post(id: Int!): Post
}
type Mutation {
createPost(input: CreatePostInput!): Post!
}
`;
const resolvers = {
Query: {
post: (_, { id }) => requestRest(`/posts/${id}`),
},
Post: {
comments: (post) => requestRest(`/posts/${post.id}/comments`),
},
Mutation: {
createPost: (_, { input }) =>
requestRest('/posts', {
method: 'POST',
body: JSON.stringify(input),
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
}),
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
});
console.log(`GraphQL server ready at ${url}`);
How does the full file fit together?
This reference combines the existing query path with the new mutation path. Comparing the full file helps you catch a misplaced brace before restarting the server.
- Start the completed server by running:
npm start
What does this restart load?
The command loads the updated schema and resolver map from index.js. The running API can now receive createPost operations.
You should see GraphQL server ready at http://localhost:4000/ in PowerShell.
- Switch back to Apollo Sandbox from earlier.
- Replace the operation editor contents with this mutation:
mutation CreatePost($input: CreatePostInput!) {
createPost(input: $input) {
id
title
body
userId
}
}
How does this operation work?
- CreatePost gives the operation a name for clearer debugging.
- $input declares the structured value required by createPost.
- The selection set asks for four fields from the returned post.
- Replace the contents of the variables dictionary panel with this object:
{
"input": {
"title": "Learning GraphQL",
"body": "A GraphQL mutation can call an existing REST API.",
"userId": 1
}
}
How do these variables reach the resolver?
- input matches the variable declared by the operation.
- The input object arrives in the resolver through its args argument.
- The resolver forwards these three values in the REST request body.
Before you execute the mutation, predict whether the returned identifier comes from your input or the REST service.
- Execute the operation from the Apollo Sandbox operation panel.
You should see the selected post fields in the response. The returned object includes id: 101.
That write path works. Your GraphQL mutation now forwards structured input to REST.
Does this create a permanent post?
JSONPlaceholder simulates this write operation. The response demonstrates the mutation flow without storing the new post permanently.
Mutation not returning a post?
- Confirm that you restarted the server after saving the new resolver.
- Check that createPost has the same spelling in the schema and resolver map.
- Compare the input object in the variables panel with the operation declaration.
Help me debug why my CreatePost mutation is not returning the expected post.
Inspect the Post type
The API can answer questions about its own type system. The __type field lets you inspect one named type directly.
- Replace the Apollo Sandbox operation editor contents with this introspection query:
query InspectPostType {
__type(name: "Post") {
name
fields {
name
}
}
}
What does this query inspect?
- InspectPostType gives the introspection operation a meaningful name.
- __type(name: "Post") asks the schema for information about Post.
- fields returns the fields that clients can select from that type.
Before you execute the query, predict whether the nested relationship appears beside the scalar post fields.
- Execute the introspection operation from the Apollo Sandbox operation panel.
You should see Post as the returned type name. Its field list includes comments.
This result proves that the running API exposes the nested relationship through its own contract.
Post fields missing?
- Confirm that Post uses the same capitalization as type Post in index.js.
- Confirm that the updated server remains running in PowerShell.
- Check that comments: [Comment!]! remains inside type Post.
Help me understand why InspectPostType does not show the comments field.
You have completed the gateway's write path. Your schema can now describe the Post contract on demand.
Secret mission
Filter Posts by User
Your gateway can fetch one post. Extend it with a filtered list for one user. Select only IDs plus titles in the response.
Clean Up Your Resources
Clean Up Your Resources
Your gateway runs entirely on your computer at no ongoing cost. Choose whether to keep its files, pause the local server, or delete the project folder.
Resources you used:
- A local Apollo Server process running on port 4000 from index.js.
- The graphql-rest-gateway folder containing package.json, package-lock.json, node_modules, rest-demo.js, and index.js.
JSONPlaceholder is absent from this checklist because its simulated writes do not persist.
Keep everything running
No action is needed. Choose this if you are still testing the gateway or want to reference the finished project later.
- Leave Apollo Server running while you continue testing the gateway at http://localhost:4000/.
- Keep the graphql-rest-gateway folder intact so your source files and installed dependencies remain ready to use.
Pause - I'll come back to this later
Stop the running server to release port 4000. Your source files and installed dependencies remain available for your return.
- Switch back to the VS Code PowerShell terminal from earlier.
- Press Ctrl+C to stop Apollo Server.
You will see the PowerShell prompt return. This confirms the local server has released port 4000.
Delete - I don't want to use this again
Deleting the folder removes your gateway code and installed project dependencies. Windows asks you to confirm before completing the permanent deletion.
- Switch back to the VS Code PowerShell terminal from earlier.
- Press Ctrl+C to stop Apollo Server.
You will see the PowerShell prompt return. The project folder is now ready to remove.
- Press the Windows key to open Windows search.
- Type File Explorer in the search box.
- Press Enter to open Windows File Explorer.
- Navigate to the location where you created graphql-rest-gateway.
- Select the graphql-rest-gateway folder.
- Press Shift+Delete to request permanent deletion.
- Confirm the permanent deletion in the Windows dialog.
You should no longer see graphql-rest-gateway in Windows File Explorer. This confirms that package.json, package-lock.json, node_modules, rest-demo.js, and index.js were removed with the folder.
Nice Work!
Nice Work!
You did it! In Apollo Sandbox, your local GraphQL gateway now returns a post with five nested comments through one operation.
What you learned:
- Built an explicit GraphQL schema for posts plus comments. Connected its fields to JSONPlaceholder REST requests through a resolver map.
- Used selection sets to control the response shape. Passed changing IDs through variables.
- Resolved nested comments from a second REST resource in one operation. Added a structured mutation for write requests. Used introspection to inspect the Post type directly from the API.
- Secret Mission: extended the GraphQL contract with posts(userId: Int!): [Post!]!. The matching resolver returns posts for one user through a REST query parameter.
Ready to quiz yourself?