As we will soon see, the code generation feature of Apollo GraphQL is one of its big advantages. Thanks to this, we will be able to ensure that the queries we write on the client-side are valid. They will be checked against the server-side GraphQL schema. Also, TypeScript interfaces will be generated for the queries and their parameters, allowing us to write type-safe code while interacting with the backend GraphQL API.
We will make our code evolve one step at a time:
- First, we're going to prepare our GraphQL queries.
- Then, we will use the Apollo CLI to generate TypeScript code.
- After that, we will create an Apollo Client inside of the Home page.
- Finally, we will use the client to search for artists and songs when the search component calls our search handler function.
Let's get to it. First of all, do the following:
- Create a new folder called graphql under frontend/src.
- Create a queries.ts file under frontend/src/graphql.
Then, add the following contents to the queries.ts file:
import gql from 'graphql-tag'; export const FindArtistsQuery = gql` query FindArtists($value: String!) { artists(name: $value) { id, name } } `; export const FindSongsQuery = gql` query FindSongs($value: String!) { songs(value: $value) { id, name, hasLyrics } } `; export const FindLyricsQuery = gql` query FindLyrics($id: String!) { songLyrics(id: $id) { id, lyrics, explicit, copyright } } `;
These are the only three queries that we will use. For all of these, a String parameter is made mandatory with the ! character. For the FindArtistsQuery and FindSongs queries, this parameter respectively corresponds to the artist and song name to look for. For the FindLyrics query, it is the identifier of the song to retrieve the lyrics for.
Each query also specifies the fields it wants to retrieve. In the case of FindSongsQuery, we ask for the hasLyrics flag to know whether there are lyrics available. We will use that information, later on, to filter out the songs for which no lyrics are available.
At this point, we don't know for sure whether our queries are valid as these are just plain strings. Let's fix that with the help of Apollo!
Open the frontend/package.json file and add the following scripts to it:
"apollo:download": "apollo schema:download --endpoint=http://localhost:4000/graphql graphql-schema.json", "apollo:generate": "apollo codegen:generate --includes=src/graphql/queries.ts --endpoint http://localhost:4000/graphql --addTypename --target typescript --globalTypesFile build/ignoreMe.txt --tagName gql --outputFlat src/generated --customScalarsPrefix lyricsFinder"
The first script, apollo:download, is a small utility that will download the GraphQL schema in JSON form from the backend server. You can run this script if you're curious about what the final schema looks like. We won't be using this file in our project though.
The second script, apollo:generate, uses the codegen:generate command of the Apollo CLI. As you can guess, its goal is to generate code for our queries, based on the GraphQL schema.
Here's an explanation of the different parameters that we make use of in the apollo:generate script:
- The includes parameter is a glob pattern that points the CLI toward the files to use as input. The CLI will read these files and identify all of the queries that it contains. Then, for each of these, it will generate code.
- The endpoint parameter points to the backend GraphQL API. Instead of this parameter, we could've used the localSchemaFile option to point to a local GraphQL schema JSON file.
- The addTypename parameter adds a typename property to the queries. Adding this instructs GraphQL to add the name of the data types in the responses (as the value of the typename property). You can learn more about this meta field here: https://graphql.org/learn/schema.
- The target flag simply defines the target language—in our case, TypeScript, of course!
- globalTypesFile is the path to a file in which global types are placed:
- Don't pay attention to the value we have used here—it is simply a workaround for the following issue: https://github.com/apollographql/apollo-tooling/issues/1179#issuecomment-501555846.
- tagName is the name of the tag we use to delimit our GraphQL queries (we will see how this works later on).
- outputFlat is an option that allows us to flatten the output. In our case, we simply put all of the generated code into the src/generated folder.
- Finally, the customScalarsPrefix option prefixes scalars. This is useful to avoid name clashes.
You can find the complete CLI reference here: https://github.com/apollographql/apollo-tooling#apollo-clientcodegen-output.
Now that you have added the code generation script, you can execute it:
yarn apollo:generate
Once done, you should find the generated code under frontend/src/generated.
Let's take a look at the FindArtists.ts file:
/* tslint:disable */ /* eslint-disable */ // This file was automatically generated and should not be edited. // ==================================================== // GraphQL query operation: FindArtists // ==================================================== export interface FindArtists_artists { __typename: "ArtistDto"; id: string; name: string; } export interface FindArtists { artists: FindArtists_artists[]; } export interface FindArtistsVariables { value: string; }
Inside of it, you'll find three different interfaces, each serving a specific purpose:
- The FindArtists_artists interface represents the datatype of each object that we will receive back in response to our GraphQL query. As you can see, we expect to receive the id and name fields. Moreover, we know for a fact that we should receive what is known as ArtistDto on the backend side.
- The FindArtists interface corresponds to the result type of the query—in this case, an array of FindArtists_artists objects (that is, the list of results).
- The FindArtistsVariables interface describes the variables that should be passed to the query.
If you look at the other generated files, you'll see that they have the same base structure.
Of course, this example is trivial, but it clearly shows that we can easily generate TypeScript code based on GraphQL queries and validated against a schema. We will later see how these interfaces can be used to increase the safety of our code.