Tutorials

Your First WunderGraph Application

In this tutorial we're going to guide you through a a very simple WunderGraph application, and we'll make it more complex later.

WunderGraph applications are just Node packages, so if you're already familiar with it you'll feel right at home. Otherwise, we'll help you with the basics.

Our very first project will build a Virtual Graph over a GraphQL which provides information about continents and countries.

This tutorial assumes some familiarity with GraphQL. If you're not familiar with it, you can take a look at their introduction .

Create and initialize your new application

For this tutorial, we'll use the WunderGraph application template, to save us some typing.

1
2
# Init a new project with the application template
npx create-wundergraph-app world -E simple

This will create our new application in the world directory. Once it finishes, cd into it and run npm i (i is a shorthand for install) to download the WunderGraph SDK.

1
cd world && npm i

WunderGraph comes with its own code generation and development server, which we will use extensively in these tutorials. At this point, we can run the development server to make sure our project is correctly initialized.

1
npm start

To check if our project is working, we can use curl to send a request:

1
curl http://localhost:9991/operations/Continents

If our project is running correctly, we should see the following response:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"data": {
"countries_continents": [
{ "name": "Africa", "code": "AF" },
{ "name": "Antarctica", "code": "AN" },
{ "name": "Asia", "code": "AS" },
{ "name": "Europe", "code": "EU" },
{ "name": "North America", "code": "NA" },
{ "name": "Oceania", "code": "OC" },
{ "name": "South America", "code": "SA" }
]
}
}

Application structure

So now that our example is up and running, we can start exploring the structure of a typical WunderGraph application.

On the top level, you can see we have package.json and tsconfig.json. These are the package manifest and the TypeScript compiler configuration, respectively, both exist in virtually any Node package using TypeScript.

Besides them there's a .wundergraph directory, which is where our WunderGraph application lives. Let's see what each entry in this directory represents

  • generated contains data and source code automatically generated by WunderGraph. You should not store these in your version control system.
  • cache contains cached data generated by WunderGraph, usually derived from fetching remote resources. Although in the general case you probably don't want to store these files, it is possible to use them to speed up CI runs. That's why we put them in a different directory.
  • wundergraph.config.ts is the main entry point of your WunderGraph application. Here we define our virtual graph as well as wiring up our server and operations configurations.
  • wundergraph.operations.ts configures our operations. Things like authentication or caching policies are stored in this file.
  • wundergraph.server.ts contains our server configuration. Here we can expose additional endpoints or define hooks that attach to server events.
  • operations is a directory containing .graphql files, where each file represents an operation that our virtual graph can expose.

Going minimal

Now we're going to reduce our app to the bare minimum and explain what each step does. Edit your wundergraph.config.ts so it looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import {
Application,
configureWunderGraphApplication,
introspect,
} from '@wundergraph/sdk'
import server from './wundergraph.server'
import operations from './wundergraph.operations'
const world = introspect.graphql({
// Namespace inside our virtual graph
apiNamespace: 'world',
// URL of the data source
url: 'https://countries.trevorblades.com/',
})
configureWunderGraphApplication({
apis: [world],
server,
operations,
})

You will notice that our WunderGraph node now starts producing some errors:

1
{"level":"error","message":"error normalizing operation: external: field: countries_continents not defined on type...

Can you spot what happened? We'll help you: we changed the namespace that our API data source maps to, notice apiNamespace used to be countries, but we changed it to world. API namespaces are used to wire our virtual graph together. Since we changed the namespace, the operations we had defined in .wundergraph/operations are now invalid. Let's remove both .wundergraph/operations/Countries.graphql and .wundergraph/operations/Continents.graphl and write a new operation.

Defining an operation

As we previously mentioned, operations are always stored in .wundergraph/operations. Each file with a .graphql extension represents an operation that our server exposes. Note that each operation is named after its file name, the query doesn't need to have an explicit name.

Since our query must end up mapping to our data sources, before writing our first operation we should open https://countries.trevorblades.com/ and visually inspect its schema.

We can see the root query has a continents field, and each Continent consists of a name, code as well as a list with its countries. For now, we're only going to include their codes.

Create .wundergraph/operations/Continents.graphl with the following contents:

1
2
3
4
5
6
query {
# Notice how the query takes the <our-api-namespace>_<data-source-namespace>
world_continents {
code
}
}

Once we save Continents.graphl WunderGraph will automatically reload our application and we can test that it is once again returning results, but now it only includes the continent code. Also, the namespace for the top level is now world.

1
2
3
# Operation name "Continents" matches the filename of the operation
# filename "Continents.graphql" without extension
curl http://localhost:9991/operations/Continents

Output:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"data": {
"world_continents": [
{ "code": "AF" },
{ "code": "AN" },
{ "code": "AS" },
{ "code": "EU" },
{ "code": "NA" },
{ "code": "OC" },
{ "code": "SA" }
]
}
}

Filtering

Our virtual graph always returns the whole list of continents. Since there are only 7, it's a problem. But what if we returning something else which had thousands or millions of entries? We would need some means to retrieve only the results that we care about. With WunderGraph, this only requires 2 new lines of code. Change Continents.graphql to:

1
2
3
4
5
6
7
8
9
# Our filter type takes the form <api-namespace>_<remote-type>
# ContinentFilterInput is the filter type as declared by
# the data source.
query Continents($filter: world_ContinentFilterInput) {
# Pass the filter to the remote API
world_continents(filter: $filter) {
code
}
}

To test this, we need to send a request with the filter. To do so, we use the wg_variables parameter:

1
curl --get --data-urlencode 'wg_variables={"filter":{"code":{"eq":"AF"}}}' http://localhost:9991/operations/Continents

Output:

1
2
3
4
5
{
"data": {
"world_continents": [{ "code": "AF" }]
}
}

Exercises

  • Expand Continents.graphql to return the country codes inside each continent
Previous
Overview

Was this article helpful to you?
Provide feedback

Edit this page