Skip to content

Commit

Permalink
lambda, cloud functions: change default path back to / (#5497)
Browse files Browse the repository at this point in the history
In Apollo Server 2, none of the serverless integrations paid much
attention to the HTTP request path (other than for health checks).  In
AS3 we rewrote the Lambda and GCF integrations on top of
`apollo-server-express` so they inherited ASE's behavior of only
responding under `/graphql`. This doesn't make that much sense for
serverless integrations, where paths are typically specified in the API
Gateway (etc) configuration, and where functions typically do one thing.
(Frankly, it doesn't make much sense for a default path to be built in
to the framework integrations either rather than letting you specify the
path when you attach the middleware to your app, but changing that in
AS3 would have been one backwards-incompatible change too many).

So we're going to change how AS3 works by saying that serverless
integrations serve on `/`. This is technically incompatible with 3.0.0
but hey, it just came out, and this is more compatible with AS2.

Improve some other docs while we're at it.

Fixes #5472.
  • Loading branch information
glasser committed Jul 16, 2021
1 parent 836a446 commit 2a3dd2e
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -11,6 +11,7 @@ The version headers in this history reflect the versions of Apollo Server itself

- `apollo-server-core`: The default `maxAge` (which defaults to 0) for a field should only be applied if no dynamic cache control hint is set. Specifically, if you call the (new in 3.0.0) function `info.cacheControl.cacheHint.restrict({ maxAge: 60 })`, it should set `maxAge` to 60 even if the default max age is lower. (This bug fix is the behavior that was intended for 3.0.0, and primarily affects the behavior of functions added in Apollo Server 3. This does mean that checking `info.cacheControl.cacheHint` now only shows explicitly-set `maxAge` and not the default, but this seems like it will be helpful since it lets you differentiate between the two similar circumstances.) [PR #5492](https://github.com/apollographql/apollo-server/pull/5492)
- `apollo-server-lambda`: Fix TypeScript types for `context` function. (In 3.0.0, the TS types for the `context` function were accidentally inherited from `apollo-server-express` instead of using the correct Lambda-specific types). [PR #5481](https://github.com/apollographql/apollo-server/pull/5481)
- `apollo-server-lambda`, `apollo-server-cloud-functions`: Make the default URL path for handling GraphQL be `/` (ie, handle all requests). This is similar to how these packages work in Apollo Server 2. After this change, `apollo-server` and the serverless integrations have a default URL path of `/` (or ignore the path entirely, in the case of `apollo-server-azure-functions`), and the framework integrations have a default URL path of `/graphql`. This is a backwards-incompatible change from 3.0.1 but minimizes the changes from Apollo Server 2 (and this AS3 change was not intended or documented). [PR #5497](https://github.com/apollographql/apollo-server/pull/5497) [Issue #5472](https://github.com/apollographql/apollo-server/issues/5472)

## v3.0.0

Expand Down
4 changes: 3 additions & 1 deletion docs/source/api/apollo-server.md
Expand Up @@ -603,7 +603,9 @@ async function startApolloServer() {

The path for Apollo Server to listen on.

The default value is `/graphql`.
The default value for framework-specific packages (`apollo-server-express`, `apollo-server-fastify`, etc) is `/graphql`.

The default value for `apollo-server` and serverless-specific packages (`apollo-server-lambda`, etc) is `/`.
</td>
</tr>

Expand Down
21 changes: 17 additions & 4 deletions docs/source/deployment/lambda.md
Expand Up @@ -81,11 +81,11 @@ functions:
handler: graphql.graphqlHandler
events:
- http:
path: graphql
path: /
method: post
cors: true
- http:
path: graphql
path: /
method: get
cors: true
```
Expand Down Expand Up @@ -116,8 +116,8 @@ stack: apollo-lambda-dev
api keys:
None
endpoints:
POST - https://ujt89xxyn3.execute-api.us-east-1.amazonaws.com/dev/graphql
GET - https://ujt89xxyn3.execute-api.us-east-1.amazonaws.com/dev/graphql
POST - https://ujt89xxyn3.execute-api.us-east-1.amazonaws.com/dev/
GET - https://ujt89xxyn3.execute-api.us-east-1.amazonaws.com/dev/
functions:
graphql: apollo-lambda-dev-graphql
```
Expand All @@ -133,6 +133,8 @@ The resulting S3 buckets and Lambda functions can be viewed and managed after lo
- To find the created S3 bucket, search the listed services for S3. For this example, the bucket created by Serverless was named `apollo-lambda-dev-serverlessdeploymentbucket-1s10e00wvoe5f`
- To find the created Lambda function, search the listed services for `Lambda`. If the list of Lambda functions is empty, or missing the newly created function, double check the region at the top right of the screen. The default region for Serverless deployments is `us-east-1` (N. Virginia)

If you changed your mind, you can remove everything from your AWS account with `npx serverless remove`.

## Customizing HTTP serving

`apollo-server-lambda` is built on top of `apollo-server-express`. It combines the HTTP server framework `express` with a package called `@vendia/serverless-express` that translates between Lambda events and Express requests. By default, this is entirely behind the scenes, but you can also provide your own express app with the `expressAppFromMiddleware` option to `createHandler`:
Expand All @@ -151,6 +153,17 @@ exports.handler = server.createHandler({
});
```

## Configuring the underlying Express integration

Because `apollo-server-lambda` is built on top of `apollo-server-express`, you can specify the same options that `apollo-server-express` accepts in `getMiddleware` (or `applyMiddleware`, other than `app`) as the `expressGetMiddlewareOptions` option to `createHandler. The default value of this option is `{path: '/'}` (and this value of `path` will be used unless you explicitly override it). For example:

```js
exports.handler = server.createHandle({
expressGetMiddlewareOptions: {
disableHealthCheck: true,
}
});

## Getting request info

Your ApolloServer's `context` function can read information about the current operation from both the original Lambda data structures and the Express request and response created by `@vendia/serverless-express`. These are provided to your `context` function as `event`, `context`, and `express` options.
Expand Down
8 changes: 7 additions & 1 deletion packages/apollo-server-cloud-functions/src/ApolloServer.ts
Expand Up @@ -30,7 +30,13 @@ export class ApolloServer extends ApolloServerExpress {
await this.ensureStarted();
if (!realHandler) {
const middleware = this.getMiddleware(
options?.expressGetMiddlewareOptions,
// By default, serverless integrations serve on root rather than
// /graphql, since serverless handlers tend to just do one thing and
// paths are generally configured as part of deploying the app.
{
path: '/',
...options?.expressGetMiddlewareOptions,
},
);
realHandler = (
options?.expressAppFromMiddleware ?? defaultExpressAppFromMiddleware
Expand Down
21 changes: 12 additions & 9 deletions packages/apollo-server-integration-testsuite/src/index.ts
Expand Up @@ -1221,17 +1221,20 @@ export default ({
});

describe('server setup', () => {
it('throws error on 404 routes', async () => {
app = await createApp();
// Serverless frameworks default listening on all paths so there's no 404.
if (!serverlessFramework) {
it('throws error on 404 routes', async () => {
app = await createApp();

const query = {
query: '{ testString }',
};
const req = request(app).get('/bogus-route').query(query);
return req.then((res) => {
expect(res.status).toEqual(404);
const query = {
query: '{ testString }',
};
const req = request(app).get('/bogus-route').query(query);
return req.then((res) => {
expect(res.status).toEqual(404);
});
});
});
}
});

if (serverlessFramework) {
Expand Down
8 changes: 7 additions & 1 deletion packages/apollo-server-lambda/src/ApolloServer.ts
Expand Up @@ -43,7 +43,13 @@ export class ApolloServer extends ApolloServerExpress<LambdaContextFunctionParam
await this.ensureStarted();
if (!realHandler) {
const middleware = this.getMiddleware(
options?.expressGetMiddlewareOptions,
// By default, serverless integrations serve on root rather than
// /graphql, since serverless handlers tend to just do one thing and
// paths are generally configured as part of deploying the app.
{
path: '/',
...options?.expressGetMiddlewareOptions,
},
);
const app = (
options?.expressAppFromMiddleware ?? defaultExpressAppFromMiddleware
Expand Down

1 comment on commit 2a3dd2e

@glasser
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, should have referenced #5462, not #5472.

Please sign in to comment.