This example shows a code-first approach to generating a GraphQL API with AWS Cloud Development Kit (CDK).
Adapted from the swapi-graphql starwars schema.
AWS Cloud Development Kit offers dynamic schema generation that reduces code duplication and allows for modularity. For example, if we look at the SWAPI schema there are several components that are similar, but not identical.
The following section will cover how we can take advantage of CDK's code-first functionality to tackle dynamic schema generation.
There are 6 object types in the SWAPI Schema:
- Film
- Person
- Planet
- Species
- Starship
- Vehicle
These object types are linked together in a graph by Connections and Edges.
Connections hold a list of target objects that the base object connects to.
For example, the FilmPersonsConnection contains a list of FilmPersonEdges
and a list of Person, where a Film is the base object and a Person is the
target object.
Edges contain the target object within the XxxConnection.
This format allows for a paginated response as users can send a query that asks
for the first X connections for a specific Film.
If we take another look at the schema, we can see there is
quite a bit of similarity between each Edge and Connection, but not enough to
utilize an interface.
For the purposes of dynamic schema generation, these are the following files we will use.
index.ts - export schema related files
object-types.ts - file containing the base object types (Film, Person, etc.)
scalar-types.ts - file containing the scalar types
utils.ts - file containing the functions to generate Connections and Edges
starwars-code-first
├── bin
│ └── starwars-code-first.ts
├── lib
│ ├── dynamic-implementation
│ │ ├── index.ts
│ │ ├── object-types.ts
│ │ ├── scalar-types.ts
│ │ └── utils.ts
│ ├── starwars-dynamic-stack.ts
│ └── ...
├── README.md
├── swapi.graphql
└── ...
Noticing the similarity between each Connection and Edge we can create a function
that generates an ObjectType based its props.
export interface baseOptions {
readonly base: ObjectType;
readonly target: ObjectType;
};
export function generateEdge(options: baseOptions): ObjectType {
const name = obtainName('Edge', options);
return new ObjectType(name, {
definition:{
node: options.target.attribute(),
cursor: required_string,
}
});
};You can find the implementation in utils.ts.
The code snippet above does the job of generating an XxxEdge Object Type dynamically.
This allows us to pass properties dynamically in our cdk-stack.ts file to construct
the schema and it's resolvers.
/**
* Generate all connection and edges for a base object type given it's targets
*/
private generateTargets(base: appsync.ObjectType, dataSource: appsync.BaseDataSource, targets: appsync.ObjectType[]): void{
targets.map((target) => {
this.generateAndAppendConnection(base, dataSource, {
base: base,
target: target,
});
});
}
/**
* Generate a Connection, Edge and Resolver for a given base and target pair
*/
private generateAndAppendConnection(base: appsync.ObjectType, dataSource: appsync.BaseDataSource, options: schema.baseOptions): void{
// Create a the Object Types for Edge and Connection
const link = schema.generateConnectionAndEdge(options);
// Determine Field Name
const fieldName = base == this.root ?
`all${pluralize(options.target.name)}` :
`${options.target.name.toLowerCase()}Connection`;
// Create Resolver and add field to base Object Type
base.addResolvableField(fieldName, link.connection.attribute(), dataSource, {
args: schema.args,
requestMappingTemplate: dummyRequest,
responseMappingTemplate: dummyResponse,
});
// Push Edges and Connections to class member variable
this.edges.push(link.edge);
this.connections.push(link.connection);
}You can find the implementation in starwars-dynamic-stack.ts.
Essentially, we can create functions that define our schema dynamically, thereby allowing for modular design for large, scalable projects.