A tool for running and testing AppSync JavaScript resolvers locally
npm install appsync-local-server




Run AWS AppSync JavaScript resolvers locally. No VTL, just JavaScript.
``bash`
npm install -g appsync-local-server
`bash`
appsync-local start -c appsync-config.json -p 4000
Server starts at http://localhost:4000/
`json`
{
"schema": "./schema.graphql",
"apiConfig": {
"auth": [{ "type": "API_KEY", "key": "dev-key" }]
},
"dataSources": [
{ "type": "NONE", "name": "LocalDS" }
],
"resolvers": [
{
"type": "Query",
"field": "getUser",
"kind": "Unit",
"dataSource": "LocalDS",
"file": "./resolvers/getUser.js"
}
]
}
Resolvers export request and response functions:
`javascript
// resolvers/getUser.js
export function request(ctx) {
return { id: ctx.arguments.id };
}
export function response(ctx) {
return { id: ctx.prev.result.id, name: 'Alice' };
}
`
Context (ctx) includes:ctx.arguments
- - GraphQL argumentsctx.prev.result
- - Result from request function (or previous pipeline function)ctx.stash
- - Shared data across pipeline functionsctx.source
- - Parent resolver resultctx.identity
- - Auth identity infoctx.request.headers
- - HTTP headers
Write your resolvers exactly as you would for AWS AppSync. Standard imports work seamlessly:
`javascript
// resolvers/createUser.js
import { util } from '@aws-appsync/utils';
import { put } from '@aws-appsync/utils/dynamodb';
export function request(ctx) {
return put({
key: { id: util.autoId() },
item: {
...ctx.arguments.input,
createdAt: util.time.nowISO8601()
}
});
}
export function response(ctx) {
return ctx.prev.result;
}
`
The same resolver code works locally and when deployed to AWS AppSync.
Globals (util, runtime, extensions) are also available without imports:
`javascript`
export function request(ctx) {
// util is a global - no import needed
return { id: util.autoId(), timestamp: util.time.nowEpochSeconds() };
}
DynamoDB helpers from @aws-appsync/utils/dynamodb:get()
- , put(), update(), remove() - Single item operationsquery()
- , scan() - Query and scan operationsbatchGet()
- , batchPut(), batchDelete() - Batch operationstransactGet()
- , transactWrite() - Transactionsoperations
- - Update expression builders (increment, append, etc.)
Install @aws-appsync/utils for TypeScript types:
`bash`
npm install --save-dev @aws-appsync/utils
`json`
{ "type": "NONE", "name": "LocalDS" }
Local DynamoDB:
`json`
{
"type": "DYNAMODB",
"name": "UsersTable",
"config": {
"tableName": "users",
"region": "us-east-1",
"endpoint": "http://localhost:8000",
"accessKeyId": "fakeId",
"secretAccessKey": "fakeSecret"
}
}
Start local DynamoDB:
`bash`
docker run -p 8000:8000 amazon/dynamodb-local
Real AWS DynamoDB:
`json`
{
"type": "DYNAMODB",
"name": "UsersTable",
"config": {
"tableName": "users",
"region": "us-east-1"
}
}endpoint
Omit to connect to real AWS. Credentials are loaded from the default AWS credential chain (env vars, ~/.aws/credentials, IAM role).
`json`
{
"type": "LAMBDA",
"name": "MyLambda",
"config": {
"functionName": "processor",
"file": "./lambdas/processor.js"
}
}
Lambda file exports a handler:
`javascript`
export async function handler(event, context) {
return { result: event.input * 2 };
}
`json`
{
"type": "HTTP",
"name": "RestAPI",
"config": {
"endpoint": "https://api.example.com",
"defaultHeaders": { "Authorization": "Bearer token" }
}
}
Resolver builds HTTP request:
`javascript/users/${ctx.arguments.id}
export function request(ctx) {
return {
method: 'GET',
resourcePath: ,
params: {
headers: { 'Accept': 'application/json' }
}
};
}
export function response(ctx) {
return ctx.prev.result.body;
}
`
Local/Direct Connection:
`json`
{
"type": "RDS",
"name": "Database",
"config": {
"engine": "postgresql",
"databaseName": "mydb",
"mode": "local",
"host": "localhost",
"port": 5432,
"user": "postgres",
"password": "password"
}
}
AWS RDS Data API:
`json`
{
"type": "RDS",
"name": "Database",
"config": {
"engine": "postgresql",
"databaseName": "mydb",
"mode": "aws",
"region": "us-east-1",
"resourceArn": "arn:aws:rds:us-east-1:123456789:cluster:my-cluster",
"awsSecretStoreArn": "arn:aws:secretsmanager:us-east-1:123456789:secret:my-secret"
}
}
Resolver executes SQL:
`javascript
export function request(ctx) {
return {
operation: 'executeStatement',
sql: 'SELECT * FROM users WHERE id = :id',
variableMap: { id: ctx.arguments.id }
};
}
export function response(ctx) {
return ctx.prev.result.records[0];
}
`
Chain multiple functions:
`json`
{
"type": "Mutation",
"field": "createUser",
"kind": "Pipeline",
"file": "./resolvers/createUser.js",
"pipelineFunctions": [
{ "file": "./functions/validate.js", "dataSource": "LocalDS" },
{ "file": "./functions/save.js", "dataSource": "UsersTable" }
]
}
Main resolver wraps the pipeline:
`javascript
// resolvers/createUser.js
export function request(ctx) {
ctx.stash.input = ctx.arguments.input;
return {};
}
export function response(ctx) {
return ctx.stash.result;
}
`
Each function in the pipeline:
`javascript
// functions/validate.js
export function request(ctx) {
if (!ctx.stash.input.email) {
throw new Error('Email required');
}
return {};
}
export function response(ctx) {
return ctx.prev.result;
}
`
json
{
"type": "API_KEY",
"key": "your-dev-key"
}
`$3
With local Lambda file:
`json
{
"type": "AWS_LAMBDA",
"lambdaFunction": "./auth/authorizer.js"
}
`With mock identity (for local development without a Lambda):
`json
{
"type": "AWS_LAMBDA",
"identity": {
"sub": "user-123",
"username": "testuser",
"groups": ["admin"]
},
"resolverContext": {
"tenantId": "tenant-abc",
"role": "admin"
}
}
`The
identity fields are available in resolvers via ctx.identity:
- ctx.identity.sub - User ID
- ctx.identity.username - Username
- ctx.identity.groups - User groups arrayThe
resolverContext is available via ctx.identity.resolverContext and merged into ctx.identity.claims.$3
`json
{
"type": "AMAZON_COGNITO_USER_POOLS",
"userPoolId": "us-east-1_xxxxx"
}
`$3
`json
{
"type": "OPENID_CONNECT",
"issuer": "https://your-issuer.com",
"clientId": "your-client-id"
}
`$3
`json
{
"type": "AWS_IAM"
}
`$3
You can configure multiple auth methods. The first one that succeeds will be used:
`json
{
"apiConfig": {
"auth": [
{ "type": "AWS_LAMBDA", "identity": { "sub": "dev-user" } },
{ "type": "API_KEY", "key": "fallback-key" }
]
}
}
`Full Example
See
examples/basic/ for a working setup.Run it:
`bash
npx appsync-local start -c examples/basic/appsync-config.json -p 4000
`Query:
`bash
curl -X POST http://localhost:4000/ \
-H "Content-Type: application/json" \
-d '{"query": "{ listUsers { id name email } }"}'
`Limitations
- JavaScript resolvers only (no VTL)
- Local simulation, not real AWS services
- No subscriptions
Development
`bash
npm install
npm run dev -- -c examples/basic/appsync-config.json -p 4000
npm test
npm run test:e2e
``