Open adambiggs opened 2 years ago
:+1: - We'd also like to see this.
A good first step would be a clean way to get the schema out of the API.
Here's what we do as a workaround (in TypeScript):
In your api-stack.ts
or whatever:
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
// Added for export of schema
import { CfnGraphQLSchema } from "aws-cdk-lib/aws-appsync";
import * as fs from "fs";
import * as prettier from "prettier"; // optional use of prettier to clean it up
const API_NAME = "API";
export interface ApiStackProps extends cdk.StackProps {
// ...
}
export class ApiStack extends cdk.Stack {
api: GraphqlApi;
constructor(scope: Construct, id: string, props: ApiStackProps) {
super(scope, id, props);
this.api = new GraphqlApi(this, API_NAME, {
// ...
});
// ADD API schema here
}
exportSchema(schemaDir: string, schemaFile: string) {
try {
const definition = this.resolve((this.api.schema as unknown as { schema: CfnGraphQLSchema }).schema.definition);
const header = `# This file is automatically generated during cdk synth
# Do not edit this file, instead edit lib/gx-graphql-schema.ts, etc.
`;
fs.mkdir(schemaDir, { recursive: true }, (err) => {
if (err) {
throw Error(`Failed to make directory ${schemaDir}: ${err}`);
}
const schema = header + definition;
// using prettier here, but the schema is in the schema variable now
prettier.resolveConfig(schemaFile).then((options) => {
if (!options) {
throw Error("Huh?");
}
const formatted = prettier.format(schema, { ...options, filepath: schemaFile });
fs.writeFile(schemaFile, formatted, (err) => {
if (err) {
throw Error(`Failed to write file ${schemaFile}: ${err}`);
}
});
});
});
} catch (err) {
console.error("Error reading out GraphQL Schema", err);
}
}
}
Then in your app:
const apiStack = new ApiStack(app, API_STACK_NAME_BASE, {
//...
});
// possibly add more to the schema here
// then export it - adding to the schema after this point is an error
apiStack.exportSchema(GENERATED_SCHEMA_DIR, GENERATED_SCHEMA_FILE);
You have to export it outside of the constructor, or you run into issues. This intentionally doesn't use async
in exportSchema
to not complicate calling it from the app with node 12 and 14.
For those who are interested, we then use the generated schema to further generate a "TypeScript Generic SDK" with GraphQL Code Generator, and further connect that to Apollo and some pre-made queries to make a ready-to-go client library.
GraphQL Code Generator "TypeScript Generic SDK" plugin notes here There's a little more work to then make use of that SDK with your GraphQL client (such as Apollo), as described at the bottom of that link.
The main problem I see here that schema becomes source of truth which always conflicts with already persisted data. We can easily delete one value from GraphQL's enum, but deleted value can still returns from database.
So the source of truth must be enum in Typescript file which leads to having two enums and mapper like MyEnum <-> ApiMyEnum
.
It would be great to generate schema using existing typescripts's types and enums.
Something like type-graphql
I agree with the thread creator this is a much needed feature for AppSync cdk. Working with code first approach is currently a very painful experience.
The main problem I see here that schema becomes source of truth which always conflicts with already persisted data. We can easily delete one value from GraphQL's enum, but deleted value can still returns from database.
This is a normal behaviour which occurs when changing your datamodel, you would need to do an migration to your data anyways.
In our project we are using the following workflow for generating type definitions from cdk in conjunction with a code first approach to compose our API with a large number of different API stacks.
After running cdk synth the schema is synthezised into a 'STACKNAME.template.json' in 'cdk.out' folder. We wrote a node script which does a lookup for the gnerated schema in the corresponding stackfile, which then generates a valid graphql.schema file in a folder called 'appsync' in the projects root folder.
const fs = require('fs');
const cdkOutDir = 'cdk.out';
fs.readdir(cdkOutDir, (e, files) => {
if (e) throw e;
for (let i = 0; i < files.length; i++) {
const file = files[i];
if (file.startsWith('YOURSTACK.template')) {
fs.readFile(`${cdkOutDir}/${file}`, 'utf8', (err, data) => {
if (err) throw err;
const definition = Object.entries(JSON.parse(data).Resources).find(
([key, _value]) => key.startsWith('APINAMESchema')
)[1].Properties.Definition;
const outDir = 'appsync';
if (!fs.existsSync(outDir)) {
fs.mkdir(outDir, (error) => {
if (error) throw error;
});
}
fs.writeFile(`${outDir}/schema.graphql`, definition, (error) => {
if (error) throw error;
});
});
console.log(file);
break;
}
}
});
After generating the graphql schema file, we installed AWS amplify cli and created a config file called '.graphqlconfig.yml' in the the newly generated 'appsync' folder :
projects:
Codegen Project:
schemaPath: schema.graphql
includes:
- ../src/graphql/**/*.ts
excludes:
- ./amplify/**
extensions:
amplify:
codeGenTarget: typescript
generatedFileName: ../src/graphql/types.ts
docsFilePath: ../src/graphql
region: us-east-1
apiId: null
frontend: javascript
framework: none
maxDepth: 4
extensions:
amplify:
version: 3
Add the following script entries to your package.json. We are using yarn but works with npm aka node as well:
"schema": "cdk synth && node scripts/generateSchema.js",
"codegen": "yarn schema && cd ./appsync && amplify codegen",`
If you now run yarn codegen -> you get the type definitions as well as queries, mutations and subscriptions generated into your project.
In semi-relation to this issue. (Please let me know if not applicable in this thread)
Is anyone aware of a way to convert a typescript type to a graphql type. Maybe by using some typescript wizardry to transform an existing type?
This will sort of achieve the same objective, but without requiring codegen as the you write the type first, then generate a graph ql type from that. You can just use the type as needed.
type User = {
id: string,
name: string,
}
// end result
type UserGQL = {
id: GraphqlType // ID!
name: GraphqlType // String!
}
schema.addType(UserGQL)
const user: User = {
id: '123',
name: 'Bob'
}
No codegen required.
I think this will be a great solution, but I might be a bit naive in regards to the TS limitations etc. (Still quite new with TS and CDK)
Description
When using the code-first schema approach, it would be very helpful if CDK could generate TypeScript type definitions to match the generated GraphQL types, similar to how GraphQL Nexus does it.
Use Case
This would be especially useful with Lambda data sources, since currently types need to be manually defined in order to use
AppSyncResolverHandler
and/orAppSyncResolverEvent
typings from the@types/aws-lambda
package.Proposed Solution
Similar to GraphQL Nexus, CDK could generate an e.g.
appsync.d.ts
file in the root of the project. And maybe the output path could be customized incdk.json
context.Other information
No response
Acknowledge