-
-
Notifications
You must be signed in to change notification settings - Fork 634
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Apollo Server Middleware #434
Comments
I tried months ago in Hono, but it failed with some dependencies in edge environment (but I already forgot what exactly it's now), Let me check it again to have a try. |
Ah, I see. Maybe it's not so straightforward. |
I think basically the blocker is in |
Thanks @ronny ,yeah, I had been doing the the same experiment, and saw the same thing. There're not only |
I tested edge runtime apollo integration for cloudflare workers recently. but TTFB is too long as it has to start server for a single request and shut it down after it reply. So not make much sense IMO |
I believe a proper wrapper for both Apollo and/or GraphQL Yoga would be a nice touch. |
Hey @yusukebe ! Can I do it ? |
Hi @FaureAlexis Thanks. But perhaps we can run Apollo Server with Of course, you can create it in your personal repository. |
I'm using hono with apollo server. @FaureAlexis this may serve as a starting point. import { StatusCode } from 'hono/utils/http-status';
const apollo = new ApolloServer();
await apollo.start();
app.on(['GET', 'POST', 'OPTIONS'], '/graphql', async ctx => {
if (ctx.req.method === 'OPTIONS') {
// prefer status 200 over 204
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS#status_code
return ctx.text('');
}
const httpGraphQLResponse = await apollo.executeHTTPGraphQLRequest({
httpGraphQLRequest: {
body: ctx.req.method === 'POST' ? await ctx.req.json() : undefined,
headers: new HeaderMap(Object.entries(ctx.req.header())),
method: ctx.req.method,
search: new URL(ctx.req.url).search,
},
context: async () => getContext(ctx), // getContext is my own graphql context factory
});
const { headers, body, status } = httpGraphQLResponse;
for (const [headerKey, headerValue] of headers) {
ctx.header(headerKey, headerValue);
}
ctx.status((status as StatusCode) ?? 200);
if (body.kind === 'complete') {
return ctx.body(body.string);
}
return ctx.body(body.asyncIterator);
}); @yusukebe What is the benefit of |
@obedm503 That's great!!
I did not try but if we use integration like cloudflare integration, we can write just like this: app.mount('/grahph', handleGraphQLRequest) But your way is better than it since we don't have to use integrations and |
Sounds good. I still have a few questions about that might help @FaureAlexis and anyone else trying to do this. How does hono handle duplicate headers? I am naively converting the object returned by const headerMap = new HeaderMap();
headers.forEach((value, key) => {
headerMap.set(key, Array.isArray(value) ? value.join(', ') : value);
}); but this would not be needed if hono already normalizes them. Also, is |
It uses the
Yes! But is stream ReadableStream? You can return a RadableStream content with return c.body(stream) If not, you may have to use |
Yeah, that's why I started the project https://github.com/metrue/EdgeQL aiming to provide a fast way to have GraphQL on edge. |
Thanks you very much @obedm503 ! Works like a charm |
For headers, I was doing like this : const headers = new HeaderMap();
c.req.raw.headers.forEach((value: string, key: string) => {
if (value) {
headers.set(key, Array.isArray(value) ? value.join(', ') : value);
}
}) |
@FaureAlexis Since the
Here's my current implementation of a The import { honoApollo } from './hono-apollo';
const app = new Hono();
const apolloServer = new ApolloServer();
await apolloServer.start();
app.route(
'/graphql',
honoApollo(apolloServer, async ctx => getContext(ctx)),
);
import {
HeaderMap,
type ApolloServer,
type BaseContext,
type ContextFunction,
type HTTPGraphQLRequest,
} from '@apollo/server';
import type { Context as HonoContext } from 'hono';
import { stream } from 'hono/streaming';
import { Hono } from 'hono/tiny';
import type { BlankSchema, Env } from 'hono/types';
import type { StatusCode } from 'hono/utils/http-status';
export function honoApollo(
server: ApolloServer<BaseContext>,
getContext?: ContextFunction<[HonoContext], BaseContext>,
): Hono<Env, BlankSchema, '/'>;
export function honoApollo<TContext extends BaseContext>(
server: ApolloServer<TContext>,
getContext: ContextFunction<[HonoContext], TContext>,
): Hono<Env, BlankSchema, '/'>;
export function honoApollo<TContext extends BaseContext>(
server: ApolloServer<TContext>,
getContext?: ContextFunction<[HonoContext], TContext>,
) {
const app = new Hono();
// Handle `OPTIONS` request
// Prefer status 200 over 204
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS#status_code
app.options('/', ctx => ctx.text(''));
// This `any` is safe because the overload above shows that context can
// only be left out if you're using BaseContext as your context, and {} is a
// valid BaseContext.
const defaultContext: ContextFunction<[HonoContext], any> = async () => ({});
const context = getContext ?? defaultContext;
app.on(['GET', 'POST'], '/', async ctx => {
const headerMap = new HeaderMap();
// Use `ctx.req.raw.headers` to avoid multiple loops and intermediate objects
ctx.req.raw.headers.forEach((value, key) => {
// When Header values are iterated over, they are automatically sorted in
// lexicographical order, and values from duplicate header names are combined.
// https://developer.mozilla.org/en-US/docs/Web/API/Headers
headerMap.set(key, value);
});
const httpGraphQLRequest: HTTPGraphQLRequest = {
// Avoid parsing the body unless necessary
body: ctx.req.method === 'POST' ? await ctx.req.json() : undefined,
headers: headerMap,
method: ctx.req.method,
search: new URL(ctx.req.url).search,
};
const httpGraphQLResponse = await server.executeHTTPGraphQLRequest({
httpGraphQLRequest,
context: () => context(ctx),
});
for (const [key, value] of httpGraphQLResponse.headers) {
ctx.header(key, value);
}
ctx.status((httpGraphQLResponse.status as StatusCode) ?? 200);
if (httpGraphQLResponse.body.kind === 'complete') {
return ctx.body(httpGraphQLResponse.body.string);
}
// This should work but remains untested
const asyncIterator = httpGraphQLResponse.body.asyncIterator;
return stream(ctx, async stream => {
for await (const part of asyncIterator) {
await stream.write(part);
}
});
});
return app;
} @yusukebe Do you have any recommendations on the best router to use here? Does it matter? |
Sorry for the super delayed response.
I don't think it matters that much which router you choose. In this case, |
did anyone get this into production and have any performance statistics? I'd love to replace express with something a little more lightweight if it's possible |
How about building Apollo Server Middleware as third-party middleware?
GraphQL middleware is available, but Apollo Sever middleware would be nice to have. Repository and npm repository name will be:
github.com/honojs/apollo-server
@honojs/apollo-server
We can use this issue apollographql/apollo-server#6034 (comment) as a reference.
The text was updated successfully, but these errors were encountered: