-
Notifications
You must be signed in to change notification settings - Fork 2k
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 2 Koa integration #1282
Conversation
Whoops, should have checked for an existing MR before trying this myself. Anyway, if there's anything here that you like, have at it: abrenneke@bb8aee9 Main differences:
Otherwise, we did everything identically. All tests are passing, though I didn't copy over |
This will be helpful in apollographql/apollo-server#1282
This will be helpful in apollographql/apollo-server#1282
This will be helpful in apollographql/apollo-server#1282
This will be helpful in apollographql/apollo-server#1282
Hi @SneakyMax,
I agree that we can have better readability with
Either
I open a PR here to export |
1de4cbb
to
311a1d7
Compare
Is it possible to use it as a middleware, same as v1? This package was so small, simple and elegant. Now it uses |
@vladshcherbin Whether we use const server = new ApolloServer({ typeDefs, resolvers });
const app = new Koa();
server.applyMiddleware({ app });
app.listen({ port: 4000 }, () =>
console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`),
); |
@chentsulin why would you want another router inside if you already have one? I understand, it can be easier for newcomers to set up server or faster to start project, but what if I already have a router and want just middleware? Koa is very minimal by its nature and this pollution with unnecessary stuff is weird. Is it possible to also export just middleware, same as v1? |
|
@vladshcherbin react-router just a implement detail now, and I am open minded to change it to a minimal middleware that handles pathes ( Every server adapters (apollo-server-hapi, apollo-server-lamda, ...) are following express implemented interface. If you just want a simple middleware, why not use v1 instead? |
@chentsulin I'd be happy to continue using simple v1 middleware, but I also want uploads out of the box. If it would be still possible to have two functions (one for gql and one for playground) which you could add to your routes, it would be super cool 😉 Something similar to how it's working now: router
.post('/graphql', koaBody(), apolloUploadKoa(), graphqlKoa(async ctx => ({
schema,
formatError,
context: {
user: await getUserFromRequest(ctx.cookies)
}
})))
.get('/graphiql', graphiqlKoa({ endpointURL: '/graphql' })) Easy to understand what's going on, what middlewares are used, no hidden magic inside. |
So, what I'm proposing is to export bare middlewares (for file uploads, graphql, health check, playground) and combined apollo server with all this inside plus cors, body parser, router, etc. Similar to how |
@vladshcherbin This could be a separated proposal to Apollo Server 2, but not the scope of this PR. IMO, if we accept this, it should apply to express and hapi too. apollo-server/packages/apollo-server-express/src/index.ts Lines 1 to 21 in 94d4d49
apollo-server/packages/apollo-server-hapi/src/index.ts Lines 1 to 21 in 94d4d49
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@chentsulin Thank you so much for the PR! This is super awesome.
I really like the decisions made with @SneakyMax's code(Thank you so much). My review tends to lean in that direction.
There are a couple comments left from the express specific days that we should take out. I'd love to get this merged and released soon!
packages/apollo-server-koa/README.md
Outdated
This is the Koa integration of GraphQL Server. Apollo Server is a community-maintained open-source GraphQL server that works with many Node.js HTTP server frameworks. [Read the docs](https://www.apollographql.com/docs/apollo-server/). [Read the CHANGELOG.](https://github.com/apollographql/apollo-server/blob/master/CHANGELOG.md) | ||
|
||
```sh | ||
npm install apollo-server-koa@rc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should also include graphql, since it's a peer dependency
(ctx: Koa.Context): GraphQLOptions | Promise<GraphQLOptions>; | ||
} | ||
|
||
// Design principles: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The principle of POST request was removed with persisted queries passed over a GET request, so we should remove these design principles
// provides typings for the integration specific behavior, ideally this would | ||
// be propagated with a generic to the super class | ||
async createGraphQLServerOptions(ctx: Koa.Context): Promise<GraphQLOptions> { | ||
return super.graphQLServerOptions(ctx); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The argument to graphQLServerOptions
is passed to the context with some additional properties spread into it, so it might make sense to pass {ctx}
or {req: ctx.req, res:ctx.res}}
. Then the context function would either be ({ ctx }) => ...
or ({ req, res }) => ...
. What do you think is more expected by Koa developers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to see the whole ctx
in the context, so ctx.request
, ctx.session
are accessible.
@@ -0,0 +1,178 @@ | |||
import * as Koa from 'koa'; | |||
import * as KoaRouter from 'koa-router'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using the middlewareFromPath
makes sense to me from SneakyMax's example sounds great, since we're not doing fine grain routing.
server: ApolloServerBase, | ||
) => (ctx: Koa.Context, next: Function) => { | ||
if (typeis(ctx.req, ['multipart/form-data'])) { | ||
return processFileUploads(ctx.req, uploadsConfig) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like async/await is much cleaner than using promises, so it sounds like a great addition.
import { GraphQLOptions, FileUploadOptions } from 'apollo-server-core'; | ||
|
||
export interface ServerRegistration { | ||
// Note: You can also pass a connect.Server here. If we changed this field to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This note is express/connect specific, so we can take it out
app: Koa; | ||
path?: string; | ||
cors?: corsMiddleware.Options | boolean; | ||
bodyParserConfig?: Object | boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's infer the Options interface for now and leave a comment pointing at DefinitelyTyped/DefinitelyTyped#27047. Thank you so much for opening that PR @chentsulin!
uploadsMiddleware = fileUploadMiddleware(this.uploadsConfig, this); | ||
} | ||
|
||
// XXX multiple paths? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was probably left over from the express integration. We should remove this, since applyMiddleware
can be called multiple times with different paths.
@vladshcherbin We definitely hear you on wanting different building blocks to put together. AS2's goal is to reduce the boilerplate necessary to build a fully functional GraphQL server. Reducing the effort to bring in features is definitely one of our goals. You're definitely still able to use the same strategy of mixing many different middleware or components together with AS1, apollo-upload-server, graphql-apq, caching, and graphql-tools. For now, we've found this approach prone to errors that can fall outside of best practice and difficult to contribute to |
@evans the reason why I'm asking it here is because this PR already has all this middlewares combined, the only thing to do is to split them into separate ones and export. It also makes transition path easier for v1 users because if you want uploads for example, just add a new upload middleware in chain and you are good to go. I'm not talking about removing combined |
Came here to second @vladshcherbin opinion on giving choice. The more I look at the new Apollo API, the more I like v1. IMHO, it looks very rigid and, more worryingly, condescending. Why the need to create a completely black, closed, non extendable box? Was it a pain point in the past having the freedom of choosing which middlewares your own server should have? I get the idea of presenting a "great out-of-the-box experience" for newcomers. But I believe it would be wiser to not do that at the expense of letting down or constricting everyone else. What are the new features that are included in |
@chentsulin and @SneakyMax Thank you so much for the PR! Really appreciate the effort ya'll put in to get this across the line. We'll release it in the next RC |
@vladshcherbin Definitely hear you on the worries. Curious if you have tried the transition, are there any pain points you ran into? Please open an issue and then we can discuss! If you're looking for uploads only, apollo-upload-server contains a koa middleware that you could import. @nfantone Apologies that what I'm saying and the api comes off as condescending. AS2's goal is ease of use and reducing time to a production server, so tries to ease the setup burden. The constructor makes setting up subscriptions and other persistent connections easier along with adding graphql-extensions. If you see areas that we could improve on, I'd be happy to explore it in an issue. Are you specifically worried about something specific? Is it loss of functionality or does AS2 prevent you from accomplishing something? |
This will be helpful in apollographql/apollo-server#1282
I've also commented on the issue raised about composable middlewares. We run an express app where we rely on composing multiple Express routers and middlewares. We cannot change this architecture but would love to upgrade to AS2. The recommendation to simply stay on AS1 is concerning. If Apollo cannot be composed or split up, you might as well just choose Express or something and bundle it by default. As in, I don't understand how to make the new Apollo Server anything but a stand alone server.
|
@evans Many thanks for you answer! Let me expand a bit on what I tried to explain earlier.
Yes, we can see that. And in that sense, I believe you did an awesome job. This is a big step in that direction from AS1. No doubt.
The problem I, personally, see with the approach you've taken is that it feels completely restrictive. While very good for adopting the framework, seems like many other present features have been hurt.
These things, the way I tend to think about them at least, seem to go in detriment of the very philosophy behind frameworks like
There is nothing simple behind that
But right there is just the thing: was that ever a pain point, to begin with? Setting up basic subscriptions or most other extensions is -luckily- pretty straightforward and documentation is already available. Unsurprisingly, these kind of things usually revolve around setting up a bunch of new middlewares in your app. Finally, I'd like to go back again to something that was already mentioned here: this looks like a missed opportunity. Exporting individual modules while still providing the same ease of use experience doesn't seem like such a hard thing to accomplish and could have pleased both sides of the crowd. |
Also, on a related but distant note, I'd like to point out that many devs (most? I don't really have an statistic - perhaps you do) do not use Typescript to develop GraphQL servers. So the fact that Typescript-specific modules are being installed each time I download |
I came to this issue by searching on google how to disable playground in apollo-server-koa. But looks like i am forced to use it. At the moment playground have some serious issue graphql/graphql-playground#790 so i can't use it. I think this is perfect example why everything should be modular. Developers want to know what is happening behind. We are not (just) worpress users :D Update Sorry i was wrong about disabling playground, i just saw https://www.apollographql.com/docs/apollo-server/migration-two-dot.html#apollo-server-2. |
I ended up extending the ApolloServer class with a custom applyMiddleware function. |
We used to get the 'graphqlKoa' and 'graphiqlKoa' before as the middle-wares, but now they have been either removed or hidden from the public. And since v2 of apollo we've faced with a BREAKING CHANGE (See:apollographql/apollo-server@dbaa465#diff-64af8fdf76996fa3ed4e498d44124800 ). In order to be compatible with the older codes users are referring, we have to do some tricks: 1) Directly refer 'graphqlKoa' from 'apollo-server-koa/dist/koaApollo' instead of 'apollo-server-koa', because it's NOT exposed through 'index.js'. 2) 'apollo-server-module-graphiql' is removed, so in order to compatible with what it was, we have to add this in our package and reuse that. And then we should rewrite 'graphiqlKoa' function as the middle-ware copied from: https://github.com/apollographql/apollo-server/blob/300c0cd12b56be439b206d55131e1b93a9e6dade/packages/apollo-server-koa/src/koaApollo.ts#L51 Notice: This change is also a BREAKING one, so please DO NOT apply to apollo's version that is less than 2.x. And there're many ambigious problems about this design of v2 for apollo (See: apollographql/apollo-server#1308). So according to chentsulin's ideas, maybe the middle-ware functions would be re-added in the future (See: apollographql/apollo-server#1282 (comment)).
The [literal `import`-ing of `express` in `ApolloServer.ts`][1] isn't new but, prior to #3047, it had only been a typing dependency — not an actual runtime dependency — since the TypeScript compiler doesn't emit `require`s when only types are utilized. To see this first hand, see [the emitted `dist/ApolloServer.js` in `[email protected]`][2] compared to [the same file in `[email protected]`][3]. (Hard to decipher, but check around like 15 and search for `"express"` in the second link.) Now that we actually utilize `express.Router` to build the composition of middleware in #3047 , it's true that `express` is no longer just a type dependency, but does warrant being a regular dependency as well! Since this has never been the case before during the entirety of the v2 line, I'm a bit concerned about introducing it now, but it seems other integrations like `apollo-server-koa` already have similar requirements going back to its introduction in [#1282][4] https://github.com/apollographql/apollo-server/blob/92ea402a90bf9817c9b887707abbd77dcf5edcb4/packages/apollo-server-koa/package.json#L41 Furthermore, we already have similar direct dependencies on other packages which we use directly, like [`cors`][5] and [`body-parser`][6]: https://github.com/apollographql/apollo-server/blob/ff3af66a0f3c63bfb056feca82fc7e7b7592b4a5/packages/apollo-server-express/package.json If this turns out to be problematic, we could consider declaring it only in `peerDependencies`, but those come with their own [share of confusion][7]. [1]: https://github.com/apollographql/apollo-server/blob/6d9c3b8c97/packages/apollo-server-express/src/ApolloServer.ts#L1 [2]: https://unpkg.com/[email protected]/dist/ApolloServer.js [3]: https://unpkg.com/[email protected]/dist/ApolloServer.js [4]: #1282: [5]: https://npm.im/cors [6]: https://npm.im/body-parser [7]: npm/rfcs#43 Fixes: #3238
The [literal `import`-ing of `express` in `ApolloServer.ts`][1] isn't new but, prior to #3047, it had only been a typing dependency — not an actual runtime dependency — since the TypeScript compiler doesn't emit `require`s when only types are utilized. To see this first hand, see [the emitted `dist/ApolloServer.js` in `[email protected]`][2] compared to [the same file in `[email protected]`][3]. (Hard to decipher, but check around like 15 and search for `"express"` in the second link.) Now that we actually utilize `express.Router` to build the composition of middleware in #3047 , it's true that `express` is no longer just a type dependency, but does warrant being a regular dependency as well! Since this has never been the case before during the entirety of the v2 line, I'm a bit concerned about introducing it now, but it seems other integrations like `apollo-server-koa` already have similar requirements going back to its introduction in [#1282][4] https://github.com/apollographql/apollo-server/blob/92ea402a90bf9817c9b887707abbd77dcf5edcb4/packages/apollo-server-koa/package.json#L41 Furthermore, we already have similar direct dependencies on other packages which we use directly, like [`cors`][5] and [`body-parser`][6]: https://github.com/apollographql/apollo-server/blob/ff3af66a0f3c63bfb056feca82fc7e7b7592b4a5/packages/apollo-server-express/package.json If this turns out to be problematic, we could consider declaring it only in `peerDependencies`, but those come with their own [share of confusion][7]. [1]: https://github.com/apollographql/apollo-server/blob/6d9c3b8c97/packages/apollo-server-express/src/ApolloServer.ts#L1 [2]: https://unpkg.com/[email protected]/dist/ApolloServer.js [3]: https://unpkg.com/[email protected]/dist/ApolloServer.js [4]: #1282: [5]: https://npm.im/cors [6]: https://npm.im/body-parser [7]: npm/rfcs#43 Fixes: #3238
Todo:
Implement it based on express one.