appsync-local-server
v1.3.1
Published
A tool for running and testing AppSync JavaScript resolvers locally
Maintainers
Readme
appsync-local
Run AWS AppSync JavaScript resolvers locally. No VTL, just JavaScript.
Install
npm install -g appsync-local-serverUsage
appsync-local start -c appsync-config.json -p 4000Server starts at http://localhost:4000/
Config File
{
"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"
}
]
}Resolver Format
Resolvers export request and response functions:
// 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
Using @aws-appsync/utils
Write your resolvers exactly as you would for AWS AppSync. Standard imports work seamlessly:
// 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.
Available Utilities
Globals (util, runtime, extensions) are also available without imports:
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.)
TypeScript Support
Install @aws-appsync/utils for TypeScript types:
npm install --save-dev @aws-appsync/utilsData Sources
NONE
For pure JavaScript logic, no external calls:
{ "type": "NONE", "name": "LocalDS" }DYNAMODB
Supports both local DynamoDB and real AWS DynamoDB.
Local DynamoDB:
{
"type": "DYNAMODB",
"name": "UsersTable",
"config": {
"tableName": "users",
"region": "us-east-1",
"endpoint": "http://localhost:8000",
"accessKeyId": "fakeId",
"secretAccessKey": "fakeSecret"
}
}Start local DynamoDB:
docker run -p 8000:8000 amazon/dynamodb-localReal AWS DynamoDB:
{
"type": "DYNAMODB",
"name": "UsersTable",
"config": {
"tableName": "users",
"region": "us-east-1"
}
}Omit endpoint to connect to real AWS. Credentials are loaded from the default AWS credential chain (env vars, ~/.aws/credentials, IAM role).
LAMBDA
Execute local JavaScript as Lambda:
{
"type": "LAMBDA",
"name": "MyLambda",
"config": {
"functionName": "processor",
"file": "./lambdas/processor.js"
}
}Lambda file exports a handler:
export async function handler(event, context) {
return { result: event.input * 2 };
}HTTP
Call external HTTP APIs:
{
"type": "HTTP",
"name": "RestAPI",
"config": {
"endpoint": "https://api.example.com",
"defaultHeaders": { "Authorization": "Bearer token" }
}
}Resolver builds HTTP request:
export function request(ctx) {
return {
method: 'GET',
resourcePath: `/users/${ctx.arguments.id}`,
params: {
headers: { 'Accept': 'application/json' }
}
};
}
export function response(ctx) {
return ctx.prev.result.body;
}RDS
Supports both direct database connections and AWS RDS Data API.
Local/Direct Connection:
{
"type": "RDS",
"name": "Database",
"config": {
"engine": "postgresql",
"databaseName": "mydb",
"mode": "local",
"host": "localhost",
"port": 5432,
"user": "postgres",
"password": "password"
}
}AWS RDS Data API:
{
"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:
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];
}Pipeline Resolvers
Chain multiple functions:
{
"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:
// 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:
// 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;
}Authentication
API Key
{
"type": "API_KEY",
"key": "your-dev-key"
}Lambda Authorizer
With local Lambda file:
{
"type": "AWS_LAMBDA",
"lambdaFunction": "./auth/authorizer.js"
}With mock identity (for local development without a Lambda):
{
"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 IDctx.identity.username- Usernamectx.identity.groups- User groups array
The resolverContext is available via ctx.identity.resolverContext and merged into ctx.identity.claims.
Cognito User Pools
{
"type": "AMAZON_COGNITO_USER_POOLS",
"userPoolId": "us-east-1_xxxxx"
}OpenID Connect
{
"type": "OPENID_CONNECT",
"issuer": "https://your-issuer.com",
"clientId": "your-client-id"
}IAM
{
"type": "AWS_IAM"
}Multiple Auth Methods
You can configure multiple auth methods. The first one that succeeds will be used:
{
"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:
npx appsync-local start -c examples/basic/appsync-config.json -p 4000Query:
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
npm install
npm run dev -- -c examples/basic/appsync-config.json -p 4000
npm test
npm run test:e2e