This example shows how you can add a GraphQL API to a WunderGraph Application and make use of the fully type safe generated client.
# .wundergraph/operations/Countries.graphqlquery {countries {codename}}
WunderGraph creates Operations using a file-based approach, similar to NextJS. Countries.graphql will generate an Endpoint named Countries.
// pages/index.tsxconst Page = () => {const countries = useQuery.Countries();return (<div>{JSON.stringify(countries)}</div>)}export default Page
The "useQuery.Countries" React hook was automatically generated from the "Countries.graphql" file. By generating the hook, there is no double declaration problem, the hook is always in sync with the Operation Definition, and the response is fully type safe.
// .wundergraph/wundergraph.config.tsconst countries = introspect.graphql({apiNamespace: "countries",url: "https://countries.trevorblades.com/",})
WunderGraph needs to understand what APIs you'd like to use in a WunderGraph Application. Therefore, you have to use the introspect API to collect information about all DataSources you'd like to use. In this simple scenario, we're adding just a single GraphQL API to virtual Graph.
WunderGraph can introspect REST APIs using OpenAPI Specification (OAS). The OAS file will be introspected and WunderGraph transforms it automatically into a GraphQL API. This way, you can stitch and combine REST APIs with GraphQL and more...
// .wundergraph/wundergraph.config.tsconst jsonPlaceholder = introspect.openApi({apiNamespace: "jsp",source: {kind: "file",filePath: "jsonplaceholder.v1.yaml",}});configureWunderGraphApplication({links: [linkBuilder.source("userPosts").target("JSP_User","posts").argument("userID", "objectField", "id").build(),linkBuilder.source("postComments").target("Post","comments").argument("postID", "objectField", "id").build(),]});
The "introspect.openAPI" can load OAS files from yaml or json and transform the REST API into a GraphQL API which is then added to the virtual Graph.
As an additional step, we can make use of the linkBuilder to add links between fields, allowing you to Query REST APIs as if they were GraphQL APIs.
The linkBuilder component is generated and supports you with autocompletion, so you don't have to guess the field names.
# .wundergraph/operations/Users.graphqlquery {users {idnamewebsiteposts {idtitlecomments {idnamebody}}}}
Thanks to the linkBuilder, we're able to build a nested Query and fetch users, their posts and all comments in a single request. WunderGraph will fetch all the data server-side and return it to the client as a single response.
// pages/index.tsxconst Page = () => {const users = useQuery.Users();return (<div>{JSON.stringify(users)}</div>)}export default Page
As a final step, we use the generated "useQuery.Users" hook in our frontend application.
This example shows how to generate an API from a PostgreSQL Database and mutate data from the frontend. In just a few lines of code, you can build a production ready chat application with realtime updates.
# .wundergraph/operations/CreatePost.graphqlmutation ($name: String! @fromClaim(name: NAME)$email: String! @fromClaim(name: EMAIL)$message: String! @jsonSchema(pattern: "^[a-zA-Z !0-9]+$")){createOnepost(data: {message: $message user: {connectOrCreate: {where: {email: $email} create: {email: $email name: $name}}}}){idmessageuser {idname}}}
This Operation creates a new Post using the generated API. If the user already exists, it will be joined using the email. If the user does not yet exist, it will be generated.
The @fromClaim directives inject the claims from the user into the Operation. Because we're using the @fromClaim directive, the Operation automatically requires the user to be authenticated.
The input for $name and $email cannot be supplied by the user, the input for $message will be validated using JSON-Schema validation.
# .wundergraph/operations/TopPosts.graphqlquery ($top: Int!) {findManypost(take: $top orderBy: [{id: desc}]){idmessageuser {idname}}}
The TopPosts Query lets us query the $top posts where $top can be supplied by the user.
// pages/index.tsximport {CreatePostForm,TopPostsLiveForm} from "../.wundergraph/generated/forms";const Index = () => {const [topMessages,setTopMessages] = useState<Response<TopMessagesResponse>>();return (<div>// the generated CreatePostForm component validates the user inputs client side using JSON-Schema validation// refetchMountedQueriesOnSuccess invalidates all Operations in the viewport if the mutation is successful<CreatePostForm liveValidate={true} refetchMountedQueriesOnSuccess={true}/>// TopPostsLiveForm is a generated that takes a "top" variable and returns the top N posts.// It is called LiveForm because it automatically updates (through server-side polling) when a new post is available.<TopPostsLiveForm onResult={setTopMessages}/>{topMessages && topMessages.status === "ok" && topMessages.body.data && topMessages.body.data.findManypost.map(post => (<div key={post.id}><p>{JSON.stringify(post)}</p></div>))}</div>)}export default Index;
This is the only code the developer has to write themselves. It's a React component that uses the generated CreatePostForm component.
You don't have to use generated form though. You could just as well use the generated TypeScript client, or the TypeScript client with generated React Hooks, or, alternatively, create your own template.
// .wundergraph/wundergraph.config.ts// introspect the database and generate an APIconst db = introspect.postgresql({apiNamespace: "db",database_querystring: "postgresql://admin:admin@localhost:54322/example?schema=public",});
This is the configuration file of your WunderGraph application. This code snipped introspects the PostgreSQL Database and generates an API.
// schema.prismadatasource db {provider = "postgresql"url = "postgresql://admin:admin@localhost:54322/example?schema=public"}model user {id Int @id @default(autoincrement())email String @uniquename Stringpost post[]}model post {id Int @id @default(autoincrement())user user @relation(fields: [userId], references: [id])message StringuserId Int}
In this scenario, we're using prisma and prisma migrate to define and migrate our database schema. You could use any equivalent database management tool though.
WunderGraph does not just support Apollo Federation. The WunderGraph server can completely replace the Apollo Federation gateway. This gives you extra security, increased throughput and last but not least, WunderGraph supports Federation with Subscriptions.
// .wundergraph/wundergraph.config.tsconst federatedApi = introspect.federation({apiNamespace: "federation",upstreams: [{url: "http://localhost:4001/graphql"},{url: "http://localhost:4002/graphql"},{url: "http://localhost:4003/graphql"},{url: "http://localhost:4004/graphql",},]});
By pointing WunderGraph towards four GraphQL Services that implement Apollo Federation, we're able to create a supergraph in seconds.
// .wundergraph/operations/PriceUpdates.graphqlsubscription {updatedPrice {upcnamepricereviews {idbodyauthor {idname}}}}
This subscription listens to updated prices from the products service. When a priceUpdate response comes back, it starts resolving the rest of the Operation from the reviews as well as the users service.
// pages/index.tsxconst Page = () => {const priceUpdates = useSubscription.PriceUpdates();return (<div>{JSON.stringify(priceUpdates)}</div>)}export default Page
WunderGraph wraps the complexity of Subscriptions in a single function call. The generated client and React hooks take care of updating the UI.
All you have to do to add authentication to your WunderGraph application is to add a config of the identity provider, everything else will be generated.
// .wundergraph/wundergraph.config.tsconfigureWunderGraphApplication({authentication: {cookieBased: {providers: [authProviders.demo(),authProviders.google({id: "google",clientId: "xxx.apps.googleusercontent.com",clientSecret: "xxx",}),],authorizedRedirectUris: ["http://localhost:3000/","http://localhost:3000/demo",]}},});
In the first step, add all the identity providers you'd like to use. We're providing a demo provider which uses GitHub in case you'd like to have a play.
const AuthenticationPage: NextPage = () => {const {user, client: {login,logout}} = useWunderGraph();return (<div><h1>Demo</h1><h2>User</h2><p>{JSON.stringify(user || "not authenticated")}</p><button onClick={() => login.github()}>Login With GitHub</button><button onClick={() => login.google()}>Login With Google</button><button onClick={() => logout()}>Logout</button></div>)}export default AuthenticationPage;
Once the identity provider is configured, you're able to use the generated client to log the user in or out.
Notice how the id argument for the identity provider lines up with "login.google", it's because the client and React hooks are generated from your configuration.
WunderGraph allows you to plug in a S3 file storage provider to easily upload files.
// .wundergraph/wundergraph.config.tsconfigureWunderGraphApplication({s3UploadProvider: [{name: "minio",endpoint: "127.0.0.1:9000",accessKeyID: "test",secretAccessKey: "12345678",bucketLocation: "eu-central-1",bucketName: "uploads",useSSL: false},{name: "do",endpoint: "fra1.digitaloceanspaces.com",accessKeyID: "xxx",secretAccessKey: "xxx",bucketLocation: "eu-central-1",bucketName: "wundergraph",useSSL: true},],});
Similarly to the authentication example, we are able to configure multiple file upload providers / buckets. As it's "just code", you could create a re-usable function to configure multiple buckets.
In this example, we're configuring two S3 providers, a locally running Minio as well as a remote storage on digitalocean.
// pages/index.tsxconst UploadPage: NextPage = () => {const [files, setFiles] = useState<FileList>();const {client} = useWunderGraph()const onFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {setFiles(e.target.files);};const onSubmit = async (e: React.FormEvent<Element>) => {e.preventDefault();const formData = new FormData();for (const key of Object.keys(files)) {formData.append("files", files[key]);}const result = await client.uploadFiles({provider: S3Provider.do,formData});console.log(result);};return (<div><form onSubmit={onSubmit}><input id="file-input" type="file" multiple onChange={onFileChange} /><button type="submit">Submit</button></form></div>);};export default UploadPage;
Once the S3 providers are configured, all you have to do is call "const result = await client.uploadFiles()" with the form data. Uploading files has never been easier.
If these examples inspired you to create something, head over to the Quickstart and start building something on your local machine.