Installation of SveteKit and Apollo Server

To create a SvelteKit application, we can follow the steps in SvelteKit documentation. Because I use node adapters, don't forget to add node adapters to the SvelteKit application.

bash
        
npm install --save-dev @sveltejs/adapter-node
      

then change the node adapter in the svelte.config.js file.

javascript
        
import adapter from '@sveltejs/adapter-node';
      

.env settings

To set environment variables in a SvelteKit application, we can create .env and .env.production files in the root of the application folder. These files will be used to store environment variables that will be used in the SvelteKit application such as connection settings to the database, secret keys, etc. Meanwhile, we only need one environment variable, namely PROJECT_PATH which will be used to store the path of the project during development and production.

Contents of the .env file:

        
NODE_ENV=development
PROJECT_PATH=.
      

Contents of the .env.production file:

        
PROJECT_PATH=.
      

Apollo Server Installation

After that, we can add Apollo Server into the SvelteKit application.

bash
        
npm install @apollo/server @apollo/subgraph graphql graphql-tag
      

Create Apollo Server

Create Schema

First, we will create a GraphQL schema that will be used by Apollo Server. This schema will define the data types that will be used by the server. We will create a schema that contains only the Query data type. This schema file will be saved in the src/schema.graphql folder.

graphql
        
type Book {
  title: Strings
  author: String
}

type Query {
  hello: String
  books: [Book]
}
      

Create Resolvers

After creating the schema, we will create resolvers that will be used by Apollo Server. These resolvers will process queries sent by the SvelteKit application. These resolvers will be stored in the src/resolvers.ts folder.

typescript
        
export default {
  Query: {
    hello: () => 'Hello World',
    books: () => {
      return [
        {
          title: 'The Awakening',
          author: 'Kate Chopin',
        },
        {
          title: 'City of Glass',
          author: 'Paul Auster',
        },
      ];
    },
  }
}
      

For the time being, we only create resolvers for Query with two queries, namely hello and books. The data in books is dummy data that we use for the example.

Create Apollo Server

After creating the schema and resolvers, we will create an Apollo Server that will be used by the SvelteKit application. This server will be accessible via the /graphql endpoint. This server file will be stored in the src/routes/graphql/+server.ts folder.

typescript
        
import { ApolloServer, HeaderMap, type BaseContext, type ContextFunction, type HTTPGraphQLRequest } from '@apollo/server';
import { readFileSync } from 'fs';
import resolvers from '../../resolvers.js';
import { parse } from 'url';
import { join } from 'path';
import { PROJECT_PATH } from '$env/static/private';

interface MyContextFunctionArgument {
  req: Request;
  res: Response;
}
interface HTTPGraphQLHead {
  status?: number;
  headers: HeaderMap;
}

type HTTPGraphQLResponseBody =
  | { kind: 'complete'; string: string }
  | { kind: 'chunked'; asyncIterator: AsyncIterableIterator<string> };

type HTTPGraphQLResponse = HTTPGraphQLHead & {
  body: HTTPGraphQLResponseBody;
};

const defaultContext: ContextFunction<[MyContextFunctionArgument], any> = async () => ({});
const context: ContextFunction<[MyContextFunctionArgument], BaseContext> = defaultContext;
      

The initial code is some import and interface that will be used by Apollo Server. Ignore if you don't understand the code. Frankly, I don't understand either, I mostly copy-pasted from the Apollo Server documentation :D.

typescript
        
let path = join(PROJECT_PATH, 'src', 'schema.graphql');
if (process.env.NODE_ENV !== 'development') {
  path = join(PROJECT_PATH, 'schema.graphql');
}
const typeDef = readFileSync(path, 'utf8');
const server = new ApolloServer({ 
  typeDefs: typeDef, 
  resolvers,
  csrfPrevention: false,
});

server.start().catch(err => console.error(err));
      

After that, we continue the code below by adding code to read the schema that we created previously. We use readFileSync to read the schema file that we created. We also added csrfPrevention: false to disable csrf prevention on Apollo Server. server.start() is used to start Apollo Server.

typescript
        
export async function POST({ request }) {
  const headers = new HeaderMap();
  for (const [key, value] of Object.entries(request.headers)) {
    if (value !== undefined) {
      headers.set(key, Array.isArray(value) ? value.join(', ') : value);
    }
  }
  const httpGraphQLRequest: HTTPGraphQLRequest = {
    method: 'POST',
    headers,
    body: await request.json(),
    search: parse(request.url).search ?? '',
  };

  const result = await server
  .executeHTTPGraphQLRequest({
    httpGraphQLRequest,
    context: () => context({ req: request, res: new Response}), // Provide a valid Response object instead of null
  });

  if (result.body.kind === 'complete') {
    // complete the response
    return new Response(result.body.string, {
      state: 200,
      headers: { 'content-type': 'application/json', },
    });
  }
  if (result.body.kind === 'chunked' && 'asyncIterator' in result.body) {
    // chunked response
    const chunkedBody = result.body as { asyncIterator: AsyncIterableIterator<any> };
    return new Response(
      new ReadableStream({
        async start(controller) {
          for await (const chunk of chunkedBody.asyncIterator) {
            controller.enqueue(chunk);
          }
          controller.close();
        },
      }), { 
        state: 200, 
        headers: { 'content-type': 'application/json', }, 
      },
    );
  }
}
      

The next code is the code to handle incoming requests to the Apollo Server. We use the POST method to handle incoming requests. We use executeHTTPGraphQLRequest to execute the query sent by the SvelteKit application. I just set the headers and status sections in Response manually, because I was in a rush

Trials

To test whether Apollo Server is running properly, we can use a graphql client application such as Insomnia or Postman. We can send hello and books queries to Apollo Server by accessing the endpoint http://localhost:5173/graphql.

graphql
        
query {
  hello
  books {
    title
    author
  }
}
      

If the Apollo Server is running well, then we will get a response from the query we sent.

Build and Deploy

For build, based on the code above, in section:

typescript
        
...
let path = join(PROJECT_PATH, 'src', 'schema.graphql');
if (process.env.NODE_ENV !== 'development') {
  path = join(PROJECT_PATH, 'schema.graphql');
}
...
      

We need to copy the schema.graphql file into the project root folder. Once the build is complete, the files are moved into the build folder (default of Vite). In the meantime, we can use scripts in package.json to copy the file. For example:

json
        
{
  ...
  "scripts": {
    "copyWindows": "copy src\\schema.graphql . && vite build && move /y schema.graphql build\\schema.graphql",
    "copyLinux": "cp src/schema.graphql . && vite build && mv schema.graphql build/schema.graphql",
    ...
  }
  ...
}
      

copyWindows for Windows and copyLinux for Linux. We can use npm run copyWindows or npm run copyLinux to copy the schema.graphql file into the project root folder, build the application, and move the schema.graphql file into the build folder.

Conclusion

The complete code is in this repository. Using Apollo Server, we can create a GraphQL server that will be used by SvelteKit applications. Apollo Server makes it easy for us to create a GraphQL server that can be used by SvelteKit applications.