@ttoss/appsync-api
This package provides a opinionated way to create an AppSync API using @ttoss/graphql-api API.
Installation
pnpm add @ttoss/appsync-api @ttoss/graphql-api graphql
Getting Started
You can create and deploy an AppSync API in four steps:
-
Create a
schemaComposerobject usinggraphql-compose, that the next steps will use to create the API. -
Create a
cloudformation.tsfile that exports a CloudFormation template usingcreateApiTemplate. UseimportValueFromParameterfrom@ttoss/cloudformationto import cross-stack values whose export names come from template parameters:
import { importValueFromParameter } from '@ttoss/cloudformation';
import { createApiTemplate } from '@ttoss/appsync-api';
import { schemaComposer } from './schemaComposer';
const template = createApiTemplate({
schemaComposer,
dataSource: {
roleArn: importValueFromParameter('AppSyncLambdaDataSourceIAMRoleArn'),
},
lambdaFunction: {
roleArn: importValueFromParameter('AppSyncLambdaFunctionIAMRoleArn'),
environment: {
variables: {
TABLE_NAME: { Ref: 'DynamoTableName' },
SHARED_SECRET: importValueFromParameter('SharedSecretExportedName'),
},
},
},
});
export default template;
- Create a
lambda.tsfile that exports a Lambda handler function usingcreateAppSyncResolverHandler:
import { createAppSyncResolverHandler } from '@ttoss/appsync-api';
import { schemaComposer } from './schemaComposer';
export const handler = createAppSyncResolverHandler({ schemaComposer });
- Add
graphqlto thelambdaExternalsarray oncarlin.yml:
lambdaExternals:
- graphql
Now you can deploy your API using carlin deploy:
carlin deploy
API
Resolvers Context
The createAppSyncResolverHandler function adds the context object to the resolvers. This object contains the following properties:
handler- AWS Lambda context object.request- AppSync request object (see Request section).identity- AppSync identity object (see Identity section).
createContext
Use createContext to enrich the resolver context once per request. Its return value is shallow-merged into the base context, making it available to every resolver. This is the recommended way to resolve per-request values like a userId from Cognito:
import { createAppSyncResolverHandler } from '@ttoss/appsync-api';
import { schemaComposer } from './schemaComposer';
import { getUserIdFromCognitoSub } from './auth';
export const handler = createAppSyncResolverHandler({
schemaComposer,
createContext: async ({ identity }) => ({
userId: await getUserIdFromCognitoSub(identity?.sub),
}),
});
Every resolver then receives context.userId without having to derive it individually.
Middlewares
You can use graphql-middleware-compatible middlewares via the middlewares option. Each middleware wraps the resolver — code before resolve() runs before the resolver, code after runs after.
In AppSync, each Lambda invocation handles a single field, so a middleware runs exactly once per request.
Use middlewares for authorization rules or cross-cutting logic (logging, tracing). Combine with createContext for per-request context enrichment:
createContext | middlewares | |
|---|---|---|
| Runs | Once per request | Once per resolver call |
| Purpose | Enrich context (e.g. userId) | Auth rules, logging, before/after logic |
| Can block execution | On error (request fails if createContext rejects/throws) | Yes (can conditionally block by not calling resolve or throwing) |
Authorization with GraphQL Shield
Use GraphQL Shield to add authorization rules:
import { allow, deny, shield } from '@ttoss/graphql-api/shield';
const permissions = shield(
{
Query: { '*': deny, me: allow },
},
{ fallbackRule: deny }
);
export const handler = createAppSyncResolverHandler({
schemaComposer,
middlewares: [permissions],
});
Custom domain name
You can add a custom domain name to your API using the customDomain option.
import { createApiTemplate } from '@ttoss/appsync-api';
export const handler = createApiTemplate({
schemaComposer,
customDomain: {
domainName: 'api.example.com', // required
certificateArn: {
'Fn::ImportValue': 'AppSyncDomainCertificateArn',
}, // required
},
});
If your domain is on Route53, you can use the option customDomain.hostedZoneName to create the required DNS records.
import { createApiTemplate } from '@ttoss/appsync-api';
export const template = createApiTemplate({
schemaComposer,
customDomain: {
domainName: 'api.example.com', // required
certificateArn: {
'Fn::ImportValue': 'AppSyncDomainCertificateArn',
}, // required
hostedZoneName: 'example.com.', // optional
},
});