Skip to content

Commit b63e551

Browse files
feat: add Apollo Server integration and update GraphQL Yoga configuration for improved functionality
1 parent 4ce82c0 commit b63e551

File tree

6 files changed

+170
-30
lines changed

6 files changed

+170
-30
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
"nitropack": "^2.11.13"
7272
},
7373
"dependencies": {
74+
"@apollo/utils.withrequired": "^3.0.0",
7475
"@as-integrations/h3": "^2.0.0",
7576
"@graphql-codegen/core": "^4.0.2",
7677
"@graphql-codegen/typescript": "^4.1.6",
@@ -102,6 +103,7 @@
102103
"@types/node": "^20.19.7",
103104
"bumpp": "^10.2.0",
104105
"changelogen": "^0.6.2",
106+
"crossws": "0.3.5",
105107
"eslint": "^9.31.0",
106108
"graphql": "^16.11.0",
107109
"graphql-yoga": "^5.15.1",

playground-nuxt/server/graphql/yoga.config.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,6 @@
11
// Custom GraphQL Yoga configuration
2-
export default defineYogaConfig({
2+
export default defineGraphQLConfig({
33
// Custom context enhancer - adds additional properties to GraphQL context
4-
context: async ({ request }) => {
5-
const event = request.$$event
6-
7-
return {
8-
event,
9-
request,
10-
storage: useStorage(),
11-
// Add custom context properties
12-
startTime: Date.now(),
13-
userAgent: event?.node?.req?.headers?.['user-agent'] || 'unknown',
14-
}
15-
},
164

175
// Additional yoga options can be added here
186
// See: https://the-guild.dev/graphql/yoga-server/docs

pnpm-lock.yaml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ export default defineNitroModule({
5656
else if (typeof rollupConfig.external === 'function') {
5757
const originalExternal = rollupConfig.external
5858
rollupConfig.external = (id, parent, isResolved) => {
59-
console.log('Checking external:', id, parent, isResolved)
6059
if (codegenExternals.some(external => id.includes(external))) {
6160
return true
6261
}
@@ -167,7 +166,6 @@ export default defineNitroModule({
167166
from: 'nitro-graphql/utils/define',
168167
imports: [
169168
'defineResolver',
170-
'defineYogaConfig',
171169
'defineMutation',
172170
'defineQuery',
173171
'defineSubscription',

src/routes/apollo-server.ts

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
1+
/*
2+
This file Copy from 'https://github.com/apollo-server-integrations/apollo-server-integration-h3/blob/main/src/index.ts'
3+
4+
There is a bug, after it is fixed, the will be used again
5+
6+
*/
7+
18
import type { BaseContext } from '@apollo/server'
29
import { defs } from '#nitro-internal-virtual/server-defs'
310
import { resolvers } from '#nitro-internal-virtual/server-resolvers'
411
import { ApolloServer } from '@apollo/server'
512
import { ApolloServerPluginLandingPageLocalDefault } from '@apollo/server/plugin/landingPage/default'
6-
import { startServerAndCreateH3Handler } from '@as-integrations/h3'
13+
// TODO: fix bug
14+
// import { startServerAndCreateH3Handler } from '@as-integrations/h3'
715
import { mergeResolvers, mergeTypeDefs } from '@graphql-tools/merge'
8-
import { defineEventHandler } from 'h3'
16+
import { startServerAndCreateH3Handler } from '../utils/apollo'
917

1018
function createMergedSchema() {
1119
try {
@@ -24,19 +32,24 @@ function createMergedSchema() {
2432
}
2533
}
2634

27-
const { typeDefs, resolvers: mergedResolvers } = createMergedSchema()
35+
let apolloServer: ApolloServer<BaseContext>
2836

29-
const apolloServer = new ApolloServer<BaseContext>({
30-
typeDefs,
31-
resolvers: mergedResolvers,
32-
introspection: true,
33-
plugins: [
34-
ApolloServerPluginLandingPageLocalDefault({ embed: true }),
35-
],
36-
})
37-
const handler = startServerAndCreateH3Handler(apolloServer, {
37+
function createApolloServer() {
38+
if (!apolloServer) {
39+
const { typeDefs, resolvers: mergedResolvers } = createMergedSchema()
40+
41+
apolloServer = new ApolloServer<BaseContext>({
42+
typeDefs,
43+
resolvers: mergedResolvers,
44+
introspection: true,
45+
plugins: [
46+
ApolloServerPluginLandingPageLocalDefault({ embed: true }),
47+
],
48+
})
49+
}
50+
return apolloServer
51+
}
52+
53+
export default startServerAndCreateH3Handler(createApolloServer, {
3854
context: async event => ({ event }),
3955
})
40-
export default defineEventHandler((event) => {
41-
return handler(event)
42-
})

src/utils/apollo.ts

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import type {
2+
ApolloServer,
3+
BaseContext,
4+
ContextFunction,
5+
HTTPGraphQLRequest,
6+
} from '@apollo/server'
7+
import type { WithRequired } from '@apollo/utils.withrequired'
8+
import type { Hooks } from 'crossws'
9+
import type {
10+
EventHandler,
11+
H3Event,
12+
HTTPMethod,
13+
RequestHeaders,
14+
} from 'h3'
15+
import { HeaderMap } from '@apollo/server'
16+
import {
17+
eventHandler,
18+
getHeaders,
19+
isMethod,
20+
readBody,
21+
setHeaders,
22+
} from 'h3'
23+
24+
export interface H3ContextFunctionArgument {
25+
event: H3Event
26+
}
27+
28+
export interface H3HandlerOptions<TContext extends BaseContext> {
29+
context?: ContextFunction<[H3ContextFunctionArgument], TContext>
30+
websocket?: Partial<Hooks>
31+
}
32+
33+
export function startServerAndCreateH3Handler(
34+
server: ApolloServer<BaseContext> | (() => ApolloServer<BaseContext>),
35+
options?: H3HandlerOptions<BaseContext>,
36+
): EventHandler
37+
export function startServerAndCreateH3Handler<TContext extends BaseContext>(
38+
server: ApolloServer<TContext> | (() => ApolloServer<TContext>),
39+
options: WithRequired<H3HandlerOptions<TContext>, 'context'>,
40+
): EventHandler
41+
export function startServerAndCreateH3Handler<TContext extends BaseContext>(
42+
server: ApolloServer<TContext> | (() => ApolloServer<TContext>),
43+
options?: H3HandlerOptions<TContext>,
44+
): EventHandler {
45+
const apolloServer = typeof server === 'function' ? server() : server
46+
apolloServer.startInBackgroundHandlingStartupErrorsByLoggingAndFailingAllRequests()
47+
48+
const defaultContext: ContextFunction<
49+
[H3ContextFunctionArgument],
50+
any
51+
> = () => Promise.resolve({})
52+
53+
const contextFunction: ContextFunction<
54+
[H3ContextFunctionArgument],
55+
TContext
56+
> = options?.context ?? defaultContext
57+
58+
return eventHandler({
59+
handler: async (event) => {
60+
// Apollo-server doesn't handle OPTIONS calls, so we have to do this on our own
61+
// https://github.com/apollographql/apollo-server/blob/fa82c1d5299c4803f9ef8ae7fa2e367eadd8c0e6/packages/server/src/runHttpQuery.ts#L182-L192
62+
if (isMethod(event, 'OPTIONS')) {
63+
// send 204 response
64+
65+
return null
66+
}
67+
68+
try {
69+
const graphqlRequest = await toGraphqlRequest(event)
70+
const { body, headers, status }
71+
= await apolloServer.executeHTTPGraphQLRequest({
72+
httpGraphQLRequest: graphqlRequest,
73+
context: () => contextFunction({ event }),
74+
})
75+
76+
if (body.kind === 'chunked') {
77+
throw new Error('Incremental delivery not implemented')
78+
}
79+
80+
setHeaders(event, Object.fromEntries(headers))
81+
event.res.statusCode = status || 200
82+
return body.string
83+
}
84+
catch (error) {
85+
if (error instanceof SyntaxError) {
86+
// This is what the apollo test suite expects
87+
event.res.statusCode = 400
88+
return error.message
89+
}
90+
else {
91+
throw error
92+
}
93+
}
94+
},
95+
websocket: options?.websocket,
96+
})
97+
}
98+
99+
async function toGraphqlRequest(event: H3Event): Promise<HTTPGraphQLRequest> {
100+
return {
101+
method: event.req.method || 'POST',
102+
headers: normalizeHeaders(getHeaders(event)),
103+
search: normalizeQueryString(event.req.url),
104+
body: await normalizeBody(event),
105+
}
106+
}
107+
108+
function normalizeHeaders(headers: RequestHeaders): HeaderMap {
109+
const headerMap = new HeaderMap()
110+
for (const [key, value] of Object.entries(headers)) {
111+
if (Array.isArray(value)) {
112+
headerMap.set(key, value.join(','))
113+
}
114+
else if (value) {
115+
headerMap.set(key, value)
116+
}
117+
}
118+
return headerMap
119+
}
120+
121+
function normalizeQueryString(url: string | undefined): string {
122+
if (!url) {
123+
return ''
124+
}
125+
return url.split('?')[1] || ''
126+
}
127+
128+
async function normalizeBody(event: H3Event): Promise<unknown> {
129+
const PayloadMethods: HTTPMethod[] = ['PATCH', 'POST', 'PUT', 'DELETE']
130+
if (isMethod(event, PayloadMethods)) {
131+
return await readBody(event)
132+
}
133+
}

0 commit comments

Comments
 (0)