Connect to data from server-side runtimes
This guide walks through how you can connect to the Amplify GraphQL API from any server-side runtimes. For Next.js applications, Amplify provides first-class support for App Router (React Server Components, Route Handlers, and Server Actions) and Pages Router (Components, API Routes), and Middleware. Review Connect to GraphQL API from AWS Lambda if need to call the Amplify GraphQL API from a Node.js AWS Lambda function.
Connect to GraphQL API from a Next.js server runtime
Before you begin, you will need:
- A Next.js application created
- Installed and configured Amplify libraries for Next.js
- Deployed a GraphQL API via the Amplify CLI, AWS CDK, or directly using AWS AppSync
Step 1 - Choose the correct GraphQL API client for Next.js server runtimes
Amplify offers two specialized GraphQL API clients for Next.js server runtimes (from @aws-amplify/adapter-nextjs/api
) that you should use depending whether you retrieve the user tokens using cookies
or NextRequest
and NextResponse
:
generateServerClientUsingCookies()
🍪 generates an API client with the Next.js'cookies
function fromnext/headers
. Each API request dynamically refetches the cookies at runtime.generateServerClientUsingReqRes()
🌐 generates an API client requiringNextRequest
andNextResponse
to provided to anrunWithAmplifyServerContext
function to prevent token contamination.
Choose the correct GraphQL API client based on your Next.js Router (App or Pages) and then their use case:
Use case | Required GraphQL client |
---|---|
React Server Component | generateServerClientUsingCookies() 🍪 |
Server Actions | generateServerClientUsingCookies() 🍪 |
Route Handler | generateServerClientUsingCookies() 🍪 |
Middleware | generateServerClientUsingReqRes() 🌐 |
Pages Router
Use case | Required GraphQL client |
---|---|
Server-side component code | generateServerClientUsingReqRes() 🌐 |
API Route | generateServerClientUsingReqRes() 🌐 |
Middleware | generateServerClientUsingReqRes() 🌐 |
Step 2 - Generate the GraphQL API client for Next.js server runtimes
To generate a GraphQL API client for the Next.js server runtime using NextRequest
and NextResponse
, you need to only provide your Amplify configuration. When making the individual API requests, you'll need to pass it into an runWithAmplifyServerContext
function to pass in the cookies from request and response variables.
import { createServerRunner } from '@aws-amplify/adapter-nextjs';import { generateServerClientUsingReqRes } from '@aws-amplify/adapter-nextjs/api';import amplifyConfig from '@/amplifyconfiguration.json';
export const { runWithAmplifyServerContext } = createServerRunner({ config: amplifyConfig});
export const reqResBasedClient = generateServerClientUsingReqRes({ config: amplifyConfig});
Step 3 - Call GraphQL API using generated server API clients
With the generated server API clients, you can make any GraphQL query or mutation request. Subscriptions are not available within server runtimes.
Import the NextRequest/NextResponse-based GraphQL API's server client in your Next.js server runtime code and make your GraphQL within the runWithAmplifyServerContext
function. Review Server-side Rendering to learn more about creating an Amplify server context.
For example, in a Next.js Pages Router API route, use the req
and res
parameters from the handler
function with runWithAmplifyServerContext
:
import type { NextApiRequest, NextApiResponse } from 'next'import type { Todo } from '@/API'import { runWithAmplifyServerContext, reqResBasedClient } from '@/utils/amplifyServerUtils'import { listTodos } from '@/graphql/queries'
type ResponseData = { todos: Todo[]}
export default async function handler( req: NextApiRequest, res: NextApiResponse<ResponseData>) { const todos = await runWithAmplifyServerContext({ nextServerContext: { req, res} operation: async (contextSpec) => { const request = await reqResBasedClient.graphql(contextSpec, { query: listTodos })
return request.data.listTodos.items } }) res.status(200).json({ todos })}
Connect to GraphQL API from AWS Lambda
You can call an AppSync GraphQL API from a Node.js app or a Lambda function. Take a basic Todo
app as an example:
type Todo @model @auth(rules: [{ allow: public }]) { name: String description: String}
This API will have operations available for Query
, Mutation
, and Subscription
. Let's take a look at how to perform both a query as well as a mutation from a Lambda function using Node.js.
Utilizing Lambda function template (IAM authorization)
First, create a Lambda function with amplify add function
and choose the AppSync - GraphQL API request (with IAM)
to get started. Be sure to grant access to your GraphQL API when prompted by the CLI to grant access to other resources in the project. Alternatively, you can create the function from scratch.
amplify add function? Select which capability you want to add: Lambda function (serverless function)? Provide an AWS Lambda function name: myfunction? Choose the runtime that you want to use: NodeJS? Choose the function template that you want to use: AppSync - GraphQL API request (with IAM)
Available advanced settings:- Resource access permissions- Scheduled recurring invocation- Lambda layers configuration- Environment variables configuration- Secret values configuration
? Do you want to configure advanced settings? Yes? Do you want to access other resources in this project from your Lambda function? Yes? Select the categories you want this function to have access to. api? Select the operations you want to permit on <YOUR_API_NAME> Query, Mutation, Subscription
You can access the following resource attributes as environment variables from your Lambda function API_<YOUR_API_NAME>_GRAPHQLAPIENDPOINTOUTPUT API_<YOUR_API_NAME>_GRAPHQLAPIIDOUTPUT API_<YOUR_API_NAME>_GRAPHQLAPIKEYOUTPUT ENV REGION
Create from scratch
amplify add function? Select which capability you want to add: Lambda function (serverless function)? Provide an AWS Lambda function name: myfunction? Choose the runtime that you want to use: NodeJS? Choose the function template that you want to use: Hello World
Available advanced settings:- Resource access permissions- Scheduled recurring invocation- Lambda layers configuration- Environment variables configuration- Secret values configuration
? Do you want to configure advanced settings? Yes? Do you want to access other resources in this project from your Lambda function? Yes? Select the categories you want this function to have access to. api? Select the operations you want to permit on <YOUR_API_NAME> Query, Mutation, Subscription
You can access the following resource attributes as environment variables from your Lambda function API_<YOUR_API_NAME>_GRAPHQLAPIENDPOINTOUTPUT API_<YOUR_API_NAME>_GRAPHQLAPIIDOUTPUT API_<YOUR_API_NAME>_GRAPHQLAPIKEYOUTPUT ENV REGION
The examples on this page use node-fetch
to make a HTTP request to your GraphQL API. When the Node.js v18 runtime is released for Lambda this dependency can be removed in favor of native fetch
To get started, add the node-fetch
module as a dependency:
CommonJS:
For functions written using CommonJS, you will need to install version 2 of node-fetch
{ "name": "myfunction", "version": "2.0.0", "description": "Lambda function generated by Amplify", "main": "index.js", "license": "Apache-2.0",+ "dependencies": {+ "node-fetch": "2"+ }, "devDependencies": { "@types/aws-lambda": "^8.10.92" }}
ESM:
{ "name": "myfunction",+ "type": "module", "version": "2.0.0", "description": "Lambda function generated by Amplify", "main": "index.js", "license": "Apache-2.0",+ "dependencies": {+ "node-fetch": "^3.2.3"+ }, "devDependencies": { "@types/aws-lambda": "^8.10.92" }}
Query
Using an API Key for authenticating your requests, you can query the GraphQL API to get a list of all Todo
s. To paginate over the list queries, you need to pass in a limit
and nextToken
on the listTodos
query. See more at GraphQL pagination .
import { default as fetch, Request } from 'node-fetch';
const GRAPHQL_ENDPOINT = process.env.API_<YOUR_API_NAME>_GRAPHQLAPIENDPOINTOUTPUT;const GRAPHQL_API_KEY = process.env.API_<YOUR_API_NAME>_GRAPHQLAPIKEYOUTPUT;
const query = /* GraphQL */ ` query LIST_TODOS { listTodos { items { id name description } } }`;
/** * @type {import('@types/aws-lambda').APIGatewayProxyHandler} */export const handler = async (event) => { console.log(`EVENT: ${JSON.stringify(event)}`);
/** @type {import('node-fetch').RequestInit} */ const options = { method: 'POST', headers: { 'x-api-key': GRAPHQL_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ query }) };
const request = new Request(GRAPHQL_ENDPOINT, options);
let statusCode = 200; let body; let response;
try { response = await fetch(request); body = await response.json(); if (body.errors) statusCode = 400; } catch (error) { statusCode = 400; body = { errors: [ { status: response.status, message: error.message, stack: error.stack } ] }; }
return { statusCode, body: JSON.stringify(body) };};
Mutation
In this example you will create a mutation showing how to pass in variables as arguments to create a Todo
record.
import { default as fetch, Request } from 'node-fetch';
const GRAPHQL_ENDPOINT = process.env.API_<YOUR_API_NAME>_GRAPHQLAPIENDPOINTOUTPUT;const GRAPHQL_API_KEY = process.env.API_<YOUR_API_NAME>_GRAPHQLAPIKEYOUTPUT;
const query = /* GraphQL */ ` mutation CREATE_TODO($input: CreateTodoInput!) { createTodo(input: $input) { id name createdAt } }`;
/** * @type {import('@types/aws-lambda').APIGatewayProxyHandler} */export const handler = async (event) => { console.log(`EVENT: ${JSON.stringify(event)}`);
const variables = { input: { name: 'Hello, Todo!' } };
/** @type {import('node-fetch').RequestInit} */ const options = { method: 'POST', headers: { 'x-api-key': GRAPHQL_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ query, variables }) };
const request = new Request(GRAPHQL_ENDPOINT, options);
let statusCode = 200; let body; let response;
try { response = await fetch(request); body = await response.json(); if (body.errors) statusCode = 400; } catch (error) { statusCode = 400; body = { errors: [ { status: response.status, message: error.message, stack: error.stack } ] }; }
return { statusCode, body: JSON.stringify(body) };};
IAM Authorization
(TK authorization rules from Lambda)
Let's take a look at another example schema that uses iam
authorization.
type Todo @model @auth(rules: [{ allow: private, provider: iam }]) { name: String description: String}
The CLI will automatically configure the Lambda execution IAM role to call the GraphQL API. Before writing your Lambda function you will first need to install the appropriate AWS SDK v3 dependencies:
{ "name": "myfunction",+ "type": "module", "version": "2.0.0", "description": "Lambda function generated by Amplify", "main": "index.js", "license": "Apache-2.0",+ "dependencies": {+ "@aws-crypto/sha256-js": "^2.0.1",+ "@aws-sdk/credential-provider-node": "^3.76.0",+ "@aws-sdk/protocol-http": "^3.58.0",+ "@aws-sdk/signature-v4": "^3.58.0",+ "node-fetch": "^3.2.3"+ }, "devDependencies": { "@types/aws-lambda": "^8.10.92" }}
Then, the following example will sign the request to call the GraphQL API using IAM authorization.
import crypto from '@aws-crypto/sha256-js';import { defaultProvider } from '@aws-sdk/credential-provider-node';import { SignatureV4 } from '@aws-sdk/signature-v4';import { HttpRequest } from '@aws-sdk/protocol-http';import { default as fetch, Request } from 'node-fetch';
const { Sha256 } = crypto;const GRAPHQL_ENDPOINT = process.env.API_<YOUR_API_NAME>_GRAPHQLAPIENDPOINTOUTPUT;const AWS_REGION = process.env.AWS_REGION || 'us-east-1';
const query = /* GraphQL */ ` query LIST_TODOS { listTodos { items { id name description } } }`;
/** * @type {import('@types/aws-lambda').APIGatewayProxyHandler} */export const handler = async (event) => { console.log(`EVENT: ${JSON.stringify(event)}`);
const endpoint = new URL(GRAPHQL_ENDPOINT);
const signer = new SignatureV4({ credentials: defaultProvider(), region: AWS_REGION, service: 'appsync', sha256: Sha256 });
const requestToBeSigned = new HttpRequest({ method: 'POST', headers: { 'Content-Type': 'application/json', host: endpoint.host }, hostname: endpoint.host, body: JSON.stringify({ query }), path: endpoint.pathname });
const signed = await signer.sign(requestToBeSigned); const request = new Request(GRAPHQL_ENDPOINT, signed);
let statusCode = 200; let body; let response;
try { response = await fetch(request); body = await response.json(); if (body.errors) statusCode = 400; } catch (error) { statusCode = 500; body = { errors: [ { message: error.message } ] }; }
return { statusCode, body: JSON.stringify(body) };};
CommonJS
When writing functions with CommonJS, you will need to install version 2 of node-fetch
:
{ "name": "myfunction", "version": "2.0.0", "description": "Lambda function generated by Amplify", "main": "index.js", "license": "Apache-2.0",+ "dependencies": {+ "@aws-crypto/sha256-js": "^2.0.1",+ "@aws-sdk/credential-provider-node": "^3.76.0",+ "@aws-sdk/protocol-http": "^3.58.0",+ "@aws-sdk/signature-v4": "^3.58.0",+ "node-fetch": "2"+ }, "devDependencies": { "@types/aws-lambda": "^8.10.92" }}
Similar to the example above you can now write your handler. The difference here is the use of require()
rather than import ... from
const { Sha256 } = require('@aws-crypto/sha256-js');const { defaultProvider } = require('@aws-sdk/credential-provider-node');const { SignatureV4 } = require('@aws-sdk/signature-v4');const { HttpRequest } = require('@aws-sdk/protocol-http');const { default: fetch, Request } = require('node-fetch');
const GRAPHQL_ENDPOINT = process.env.API_ < YOUR_API_NAME > _GRAPHQLAPIENDPOINTOUTPUT;const AWS_REGION = process.env.AWS_REGION || 'us-east-1';
const query = /* GraphQL */ ` query LIST_TODOS { listTodos { items { id name description } } }`;
/** * @type {import('@types/aws-lambda').APIGatewayProxyHandler} */exports.handler = async (event) => { console.log(`EVENT: ${JSON.stringify(event)}`);
const endpoint = new URL(GRAPHQL_ENDPOINT);
const signer = new SignatureV4({ credentials: defaultProvider(), region: AWS_REGION, service: 'appsync', sha256: Sha256 });
const requestToBeSigned = new HttpRequest({ method: 'POST', headers: { 'Content-Type': 'application/json', host: endpoint.host }, hostname: endpoint.host, body: JSON.stringify({ query }), path: endpoint.pathname });
const signed = await signer.sign(requestToBeSigned); const request = new Request(GRAPHQL_ENDPOINT, signed);
let statusCode = 200; let body; let response;
try { response = await fetch(request); body = await response.json(); if (body.errors) statusCode = 400; } catch (error) { statusCode = 500; body = { errors: [ { message: error.message } ] }; }
return { statusCode, body: JSON.stringify(body) };};