Setting up a Typed GraphQL client with SvelteKit and TypeScript anno 2023

Kishan Nirghin
5 min readJan 2, 2023

This guide will help you setup a GraphQL client using SvelteKit. For this guide we assume that the GraphQL API already exists and is configured properly.

SvelteKit + GraphQL

Now as with most modern development getting something done quick/pragmatic is by picking the right tool for the job. Same goes for this GraphQL Client. There a numerous GraphQL clients out there all trying to gain traction and become relevant. The important bit here is to understand that these tools will come and go.

The tool that I ended up with is the trusted apollo-client which is most familiar for the React’ers out there. As for my specific case I wanted my application to be able to communicate with multiple different protected GraphQL endpoints. Most out-of-the-box tools weren’t applicable for this as they usually target a dedicated backend-for-frontend (thus a single GraphQL api).

Getting the GraphQL Schema

GraphQL API’s usually ship with a schema definition that is accessible from the API itself. To stick with the apollo landscape an easy way to acquire the schema.graphql is with the Rover tool.

Once you’ve acquired Rover on your local machine getting the schema is as simple as:

> rover graph introspect http://localhost:4000/graphql > schema.graphql

Now if you own the API there are probably much better ways of doing this, however if you’re working with a public API and just want the schema quickly the above command will do the trick.

Setting up the Client

Once the Schema is in we have our first step at getting a strong typed GraphQL API. Next up would be configuring the client such that it is compatible with SvelteKit.

import { env } from "$env/dynamic/private";

/* Important to import from index.js to solve Module issues */
import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client/core/index.js";

/**
Returns a GraphQL Client that is ready to use.
*/
export function getClient(apiEndpoint) {
const httpLink = new HttpLink({
uri: apiEndpoint,
headers: {
Authorization: `Bearer ${env.SECRET_TOKEN}`,
},
});

const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache({
/* Workaround to deal with query errors that can appear */
addTypename: false
}),
});

return client;
}

The API I’m using requires a JWT token to authenticate. Adding the Authorization header to the HttpLink ensures that the token is sent over with each request made using this client.

This function can be used in any ServerSideRendered piece of code of SvelteKit. Note: it is possible to run similar code on the client-side (thus in the browser) but this would be a security risk as it would expose your secret-token to each user.

Setup Response Types

The next step is to use the client to perform queries and mutations on the GraphQL API.

import { gql } from "@apollo/client/core/index.js";

const query = gql`
query findUsers($userId: ID!, $role: String!) {
namedResult: findUsers(
userId: { is: $userId },
userRoles: { contains: [$role] }
) {
id
username
email
avatarUrl
}
}`

After writing the query inside the gql template block the return type definition of this query can be generated using graphql-codegen .

Among other things, GraphQL CodeGen can take the API schema and all the GraphQL queries as input and will output the expected response types for those queries.

schema:
- schema.graphql
documents:
- src/randomproject.api.ts
generates:
src/_generated/operation_types.ts:
plugins:
- typescript
- typescript-operations

The above codegen.yml will be picked up automatically when running npx graphl-codegen and it will output the response types of your queries into src/_generated/operation_types.ts

This means that every time a GraphQL query or mutation changes you’d need to re-compile the operation_types.ts to be up-to-date.

Note that the `documents` section of the codegen.yml should contain ALL files that contain graphql queries or mutations for which you’d like to generate type-defintions.

Executing the queries and mutations

Now all puzzle pieces should start to fall in place, the last thing to do is actually execute a query using the client we built above and apply the generated types to the results.

import { gql } from "@apollo/client/core/index.js";
import type { FindUsersQuery } from "./_generated/operation_types"

function findUser(userId: string, role: string): number {
const query = gql`
query findUsers($userId: ID!, $role: String!) {
namedResult: findUsers(
userId: { is: $userId },
userRoles: { contains: [$role] }
) {
id
username
email
avatarUrl
}
}`

const { data } = await client.query<FindUsersQuery>({
query: query,
variables: {
userId,
role,
},
/* Disabled caches, more usefull for very dynamic data */
fetchPolicy: "no-cache",
});

/* 'data' is automatically (strongly) typed */
const id = data.id;
return id;
}

The function above is an example of on how this kinda works.

Mutations are fairly similar:

import { gql } from "@apollo/client/core/index.js";
import type { InsertEntryMutation } from "./_generated/operation_types"

export async function insertEntry(
userId: string,
requestId: string,
) {
const query = gql`
mutation InsertEntry(
$userId: ID
$requestId: ID
) {
entries {
create(
user: { id: { is: $userId } }
devRequest: { id: { is: $devRequestId } }
) {
message
}
}
}
`;

const { data } = await client.mutate<InsertEntryMutation>({
mutation: query,
variables: {
userId,
requestId,
},
});

return data; /* 'data' is strongly typed */
}

That is effectively it. The above examples should demonstrate how to setup a strong typed GraphQL client using SvelteKit. However since we’re not using any SvelteKit specific tooling this approach will work for nearly all JavaScript/TypeScript based code.

To summarise:

  • To kick off we’ve used Roverto extract the graphl.schema from a public API.
  • Then went on to setup the GraphQL client using the open-source apollo-client with a small workaround to make it compatible with JavaScript modules by importing from “@apollo/client/core/index.js”
  • Added a JWT to be send with each request to the API by adding a header to the GraphlQL client.
  • We generated types based on our queries and mutations
  • Lastly we’ve demonstrated how this all glues together by providing an example of a query and a mutation.

I hope this post has been of any use. If so, or if you have any questions feel free to let me know.

--

--