Deploying with Azure Functions

How to deploy Apollo Server with Azure Functions


Azure Functions is a serverless computing platform with a pay-for-use billing model that enables you to run code without worrying about provisioning or managing servers.

In this guide, we'll walk through how to deploy Apollo Server's Azure Functions integration to Azure Functions.

Prerequisites

Make sure you've completed the following before proceeding with this guide:

⚠️ This integration requires Azure Functions v4 or later and Node.js v22 or later. If you're using an older version, please upgrade before proceeding.

Set up your project

For this example, we'll start from scratch to show how all the pieces fit together.

Create a new Azure Functions app

First, create a new Azure Functions app using the v4 programming model. You can follow the official guide to create an Azure Function with TypeScript (or JavaScript).

Install dependencies

Install the necessary packages for using Apollo Server and its integration for Azure Functions:

shell
1npm install @apollo/server graphql @as-integrations/azure-functions @azure/functions

If you're using TypeScript:

shell
1npm install -D typescript

Create your GraphQL function

Create a new HTTP Trigger function for your GraphQL endpoint. Update your function file (e.g., src/functions/graphql.ts) to use the Apollo integration:

TypeScript
src/functions/graphql.ts
1import { ApolloServer } from '@apollo/server';
2import { app } from '@azure/functions';
3import { startServerAndCreateHandler } from '@as-integrations/azure-functions';
4
5// The GraphQL schema
6const typeDefs = `#graphql
7  type Query {
8    hello: String
9  }
10`;
11
12const resolvers = {
13  Query: {
14    hello: () => 'world',
15  },
16};
17
18// Set up Apollo Server
19const server = new ApolloServer({
20  typeDefs,
21  resolvers,
22});
23
24app.http('graphql', {
25  handler: startServerAndCreateHandler(server),
26});

Run your app locally

Run the Azure Functions app locally using the Azure Functions Core Tools:

Bash
1func start

Go to the function endpoint (for example, http://localhost:7071/api/graphql) to access your GraphQL server using Apollo Sandbox.

Use context

You can pass custom context to your resolvers to handle authentication, authorization, and other request-specific data:

TypeScript
src/functions/graphql.ts
1import { ApolloServer } from '@apollo/server';
2import { app, HttpRequest, InvocationContext } from '@azure/functions';
3import { startServerAndCreateHandler } from '@as-integrations/azure-functions';
4
5// Define the context type
6type MyContext = {
7  user: string | null;
8  isAuthenticated: boolean;
9};
10
11// Context creation function
12async function createContext(
13  req: HttpRequest,
14  _context: InvocationContext,
15  _body: any,
16): Promise<MyContext> {
17  const authHeader = req.headers.get('authorization');
18
19  return {
20    user: authHeader ? authHeader : null,
21    isAuthenticated: authHeader !== null,
22  };
23}
24
25const typeDefs = `#graphql
26  type Query {
27    hello: String
28    me: User
29  }
30
31  type User {
32    id: ID!
33    name: String!
34  }
35`;
36
37const resolvers = {
38  Query: {
39    hello: () => 'world',
40    me: (_: any, _args: any, ctx: MyContext) => {
41      if (!ctx.isAuthenticated) {
42        throw new Error('Not authenticated');
43      }
44      return { id: '1', name: ctx.user };
45    },
46  },
47};
48
49const server = new ApolloServer<MyContext>({
50  typeDefs,
51  resolvers,
52});
53
54app.http('graphql', {
55  handler: startServerAndCreateHandler(server, {
56    context: async (args) => createContext(args.req, args.context, args.body),
57  }),
58});

Your context function receives an object with these properties:

  • req: The HttpRequest object containing HTTP headers, method, body, query parameters, etc.

  • context: The InvocationContext object containing function execution context (trace context, retry context, etc.)

  • body: The parsed request body

Deployment

You have several options for deploying your GraphQL API to Azure:

Using VS Code

  1. Install the Azure Functions extension.

  2. Open the context window (use right-click) of your function app in the Azure Functions panel.

  3. Select the Deploy to Function App option.

Using Azure CLI

  1. Build your application:

Bash
1npm run build
  1. Deploy using the Azure Functions Core Tools:

Bash
1func azure functionapp publish <YOUR_FUNCTION_APP_NAME>

Using CI/CD

Configure automated deployments using:

For more details, see the Azure Functions deployment documentation.

Using function information

You can access the Azure Functions request and invocation context information within your resolvers through the context:

TypeScript
1import { ApolloServer } from '@apollo/server';
2import { app, HttpRequest, InvocationContext } from '@azure/functions';
3import { startServerAndCreateHandler } from '@as-integrations/azure-functions';
4
5type MyContext = {
6  req: HttpRequest;
7  functionContext: InvocationContext;
8};
9
10const server = new ApolloServer<MyContext>({
11  typeDefs,
12  resolvers,
13});
14
15app.http('graphql', {
16  handler: startServerAndCreateHandler(server, {
17    context: async ({ req, context }) => {
18      return {
19        req,
20        functionContext: context,
21      };
22    },
23  }),
24});

The req object contains the HTTP request information (headers, method, body, query parameters, etc.). The context object (not to be confused with the context function) contains the Azure Functions invocation context (trace context, retry context, etc.).

Advanced patterns

Authentication and authorization

You can implement authentication and authorization patterns in your context function:

TypeScript
1import { ApolloServer } from '@apollo/server';
2import { GraphQLError } from 'graphql';
3import { app, HttpRequest, InvocationContext } from '@azure/functions';
4import { startServerAndCreateHandler } from '@as-integrations/azure-functions';
5
6type User = {
7  id: string;
8  name: string;
9  email: string;
10  roles: string[];
11};
12
13type Context = {
14  user: User | null;
15  isAuthenticated: boolean;
16};
17
18// Helper function to validate and decode token
19async function getUserFromToken(token: string): Promise<User | null> {
20  // In a real app, this would:
21  // 1. Verify JWT signature
22  // 2. Check expiration
23  // 3. Look up user in database
24
25  // Mock implementation
26  if (token.startsWith('Bearer ')) {
27    const userId = token.replace('Bearer ', '');
28    // Look up user...
29    return { id: userId, name: 'User', email: 'user@example.com', roles: ['user'] };
30  }
31
32  return null;
33}
34
35// Context creation function
36async function createContext(
37  req: HttpRequest,
38  _context: InvocationContext,
39  _body: any,
40): Promise<Context> {
41  const authHeader = req.headers.get('authorization');
42
43  let user: User | null = null;
44
45  if (authHeader) {
46    try {
47      user = await getUserFromToken(authHeader);
48    } catch (error) {
49      console.error('Authentication error:', error);
50    }
51  }
52
53  return {
54    user,
55    isAuthenticated: user !== null,
56  };
57}
58
59// Helper function to ensure user is authenticated
60function requireAuth(
61  ctx: Context,
62): asserts ctx is Context & { user: NonNullable<Context['user']> } {
63  if (!ctx.isAuthenticated || !ctx.user) {
64    throw new GraphQLError('You must be logged in to access this resource', {
65      extensions: {
66        code: 'UNAUTHENTICATED',
67        http: { status: 401 },
68      },
69    });
70  }
71}
72
73// Helper function to check if user has required role
74function requireRole(ctx: Context, role: string): void {
75  requireAuth(ctx);
76
77  if (!ctx.user.roles.includes(role)) {
78    throw new GraphQLError(
79      `You need the '${role}' role to access this resource`,
80      {
81        extensions: {
82          code: 'FORBIDDEN',
83          http: { status: 403 },
84        },
85      },
86    );
87  }
88}
89
90const typeDefs = `#graphql
91  type User {
92    id: ID!
93    name: String!
94    email: String!
95    roles: [String!]!
96  }
97
98  type Query {
99    hello: String
100    me: User
101    adminData: String
102  }
103`;
104
105const resolvers = {
106  Query: {
107    hello: () => 'world',
108    me: (_: any, _args: any, ctx: Context) => {
109      requireAuth(ctx);
110      return ctx.user;
111    },
112    adminData: (_: any, _args: any, ctx: Context) => {
113      requireRole(ctx, 'admin');
114      return 'Secret admin data';
115    },
116  },
117};
118
119const server = new ApolloServer<Context>({
120  typeDefs,
121  resolvers,
122});
123
124app.http('graphql', {
125  handler: startServerAndCreateHandler(server, {
126    context: async (args) => createContext(args.req, args.context, args.body),
127  }),
128});

Additional resources

Feedback

Edit on GitHub

Ask Community