From bd003eeb56a161d7cda3181c9473cf2bfe1020fa Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 17 Jun 2021 15:16:34 -0700 Subject: [PATCH] Other things that may be worth fixing, marked with FIXME These are not AS3 regressions. --- docs/source/api/apollo-server.md | 1 + docs/source/data/data-sources.mdx | 2 +- docs/source/data/resolvers.mdx | 6 +++--- docs/source/integrations/middleware.md | 3 +++ docs/source/monitoring/health-checks.md | 1 + docs/source/performance/caching.md | 4 +++- docs/source/proxy-configuration.md | 2 ++ docs/source/schema/custom-scalars.md | 8 ++++++-- docs/source/schema/directives.md | 1 + docs/source/schema/schema.md | 23 ++++++++++++++++------- docs/source/schema/unions-interfaces.md | 9 +++++++-- docs/source/security/terminating-ssl.md | 2 ++ 12 files changed, 46 insertions(+), 16 deletions(-) diff --git a/docs/source/api/apollo-server.md b/docs/source/api/apollo-server.md index 49437b4b023..540fb310480 100644 --- a/docs/source/api/apollo-server.md +++ b/docs/source/api/apollo-server.md @@ -5,6 +5,7 @@ api_reference: true --- This API reference documents the exports from the `apollo-server` package. +FIXME no it doesn't, it's a weird mishmosh of apollo-server and the integrations. We should refactor it somehow. ## `class ApolloServer` diff --git a/docs/source/data/data-sources.mdx b/docs/source/data/data-sources.mdx index 191c539d2d7..8be48ebf1de 100644 --- a/docs/source/data/data-sources.mdx +++ b/docs/source/data/data-sources.mdx @@ -30,7 +30,7 @@ flowchart LR; ## Open-source implementations -All data source implementations extend the generic [`DataSource` abstract class](https://github.com/apollographql/apollo-server/blob/main/packages/apollo-datasource/src/index.ts), which is included in the `apollo-server` library. Subclasses define whatever logic is required to communicate with a particular store or API. +All data source implementations extend the generic [`DataSource` abstract class](https://github.com/apollographql/apollo-server/blob/main/packages/apollo-datasource/src/index.ts), which is included in the `apollo-server` library. FIXME it's in the `apollo-server` repo but it's in its own `apollo-datasource` package! Subclasses define whatever logic is required to communicate with a particular store or API. Apollo and the larger community maintain the following open-source implementatons: diff --git a/docs/source/data/resolvers.mdx b/docs/source/data/resolvers.mdx index 8bf39ba061b..2b8473ee5bd 100644 --- a/docs/source/data/resolvers.mdx +++ b/docs/source/data/resolvers.mdx @@ -164,7 +164,7 @@ Note that you can define your resolvers across as many different files and objec ## Resolver chains -Whenever a query asks for a field that contains an object type, the query _also_ asks for _at least one field_ of that object (if it didn't, there would be no reason to include the object in the query). A query always "bottoms out" on fields that contain either a scalar or a list of scalars. +Whenever a query asks for a field that contains an object type, the query _also_ asks for _at least one field_ of that object (if it didn't, there would be no reason to include the object in the query). A query always "bottoms out" on fields that contain either a scalar or a list of scalars. FIXME technically can also be things like "list of list of scalars", not to mention non-nullable, and also there are enums which aren't technically scalars. Therefore, whenever Apollo Server _resolves_ a field that contains an object type, it always then resolves one or more fields of that object. Those subfields might in turn _also_ contain object types. Depending on your schema, this object-field pattern can continue to an arbitrary depth, creating what's called a **resolver chain**. @@ -388,7 +388,7 @@ const server = new ApolloServer({ } ``` -> The fields of the object passed to your `context` function differ if you're using middleware besides Express. [See the API reference for details.](/api/apollo-server/#middleware-specific-context-fields) +> The fields of the object passed to your `context` function differ if you're using middleware besides Express. [See the API reference for details.](/api/apollo-server/#middleware-specific-context-fields) FIXME it's not really clear here that `apollo-server` wraps Express and so it gets the Express context args. Also we've barely mentioned middleware so far in the docs. Context initialization can be asynchronous, allowing database connections and other operations to complete: @@ -411,7 +411,7 @@ A resolver function's return value is treated differently by Apollo Server depen |---|---| | Scalar / object |

A resolver can return a single value or an object, as shown in [Defining a resolver](#defining-a-resolver). This return value is passed down to any nested resolvers via the `parent` argument.

| | `Array` |

Return an array if and only if your schema indicates that the resolver's associated field contains a list.

After you return an array, Apollo Server executes nested resolvers for each item in the array.

| -| `null` / `undefined` |

Indicates that the value for the field could not be found.

If your schema indicates that this resolver's field is nullable, then the operation result has a `null` value at the field's position.

If this resolver's field is _not_ nullable, Apollo Server sets the field's _parent_ to `null`. If necessary, this process continues up the resolver chain until it reaches a field that _is_ nullable. This ensures that a response never includes a `null` value for a non-nullable field.

| +| `null` / `undefined` |

Indicates that the value for the field could not be found.

If your schema indicates that this resolver's field is nullable, then the operation result has a `null` value at the field's position.

If this resolver's field is _not_ nullable, Apollo Server sets the field's _parent_ to `null`. If necessary, this process continues up the resolver chain until it reaches a field that _is_ nullable. This ensures that a response never includes a `null` value for a non-nullable field. FIXME this should mention that if this happens, an error is returned too!

| | [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises) |

Resolvers often perform asynchronous actions, such as fetching from a database or back-end API. To support this, a resolver can return a promise that resolves to any other supported return type.

| diff --git a/docs/source/integrations/middleware.md b/docs/source/integrations/middleware.md index 961b411298f..0d6227a9709 100644 --- a/docs/source/integrations/middleware.md +++ b/docs/source/integrations/middleware.md @@ -4,6 +4,9 @@ sidebar_title: Node.js middleware description: Use Apollo Server with Express, Koa, and more --- +FIXME the way we document the packages needs some work in general. first of all the idea that there are multiple packages at all isn't really introduced early in the docs. secondly, it's not always applyMiddleware, there's createHandler too. we should actually document all the middlewares!!! or give up and explicitly only care about the READMEs. Not an AS3 regression though. Also we shouldn't refer to `apollo-server` as the "core" package since it's not `apollo-server-core`. I like "batteries-included". +Another alternative is that we should endeavour to document Express explicitly and basically say "for the others, figure it out yourself" (or README only) + Apollo Server integrates easily with several popular Node.js middleware libraries. To integrate, first install the appropriate package from the table below _instead of_ the core `apollo-server` package: diff --git a/docs/source/monitoring/health-checks.md b/docs/source/monitoring/health-checks.md index 4be22d2bc49..b2d3d71c688 100644 --- a/docs/source/monitoring/health-checks.md +++ b/docs/source/monitoring/health-checks.md @@ -8,6 +8,7 @@ Health checks are often used by load balancers to determine if a server is avail This basic health check may not be comprehensive enough for some applications and depending on individual circumstances, it may be beneficial to provide a more thorough implementation by defining an `onHealthCheck` function to the `ApolloServer` constructor options. If defined, this `onHealthCheck` function should return a `Promise` which _rejects_ if there is an error, or _resolves_ if the server is deemed _ready_. A `Promise` _rejection_ will result in an HTTP status code of 503, and a _resolution_ will result in an HTTP status code of 200, which is generally desired by most health-check tooling (e.g. Kubernetes, AWS, etc.). > **Note:** Alternatively, the `onHealthCheck` can be defined as an `async` function which `throw`s if it encounters an error and returns when conditions are considered normal. +FIXME we should just document this as being an async function and use an async function in the example ```js{10-19} const { ApolloServer, gql } = require('apollo-server'); diff --git a/docs/source/performance/caching.md b/docs/source/performance/caching.md index ae8b1cd4f06..535285ab6af 100644 --- a/docs/source/performance/caching.md +++ b/docs/source/performance/caching.md @@ -4,6 +4,8 @@ sidebar_title: Caching description: Configure caching behavior on a per-field basis --- +FIXME should note that federation doesn't support caching well though maybe link to something that explains workarounds? + Apollo Server enables you to define cache control settings (`maxAge` and `scope`) for each field in your schema: ```graphql{5,7} @@ -214,7 +216,7 @@ If you run Apollo Server behind a CDN or another caching proxy, you can configur Because CDNs and caching proxies only cache GET requests (not POST requests, which Apollo Client sends for all operations by default), we recommend enabling [automatic persisted queries](./apq/) and the [`useGETForHashedQueries` option](./apq/) in Apollo Client. -Alternatively, you can set the `useGETForQueries` option of [HttpLink](https://www.apollographql.com/docs/react/api/link/apollo-link-http) in your `ApolloClient` instance, but **this is less secure** because your query string and GraphQL variables are sent as plaintext URL query parameters. +Alternatively, you can set the `useGETForQueries` option of [HttpLink](https://www.apollographql.com/docs/react/api/link/apollo-link-http) in your `ApolloClient` instance, but **this may be less secure** because your query string and GraphQL variables are sent as plaintext URL query parameters which are more likely to be saved in logs by some server or proxy between the user and your GraphQL server. FIXME I tried to improve this but it still doesn't make that much sense because `useGETForHashedQueries` certainly puts variables in the URL... I think the real reason to avoid useGETForQueries is that GETs have a length limit! ### Disabling `Cache-Control` diff --git a/docs/source/proxy-configuration.md b/docs/source/proxy-configuration.md index 5fdae327863..8c2da891d6d 100644 --- a/docs/source/proxy-configuration.md +++ b/docs/source/proxy-configuration.md @@ -5,6 +5,8 @@ description: Configuring proxy settings for outgoing requests Certain features of the Apollo platform require Apollo Server to make outgoing requests to Apollo Studio. These include: +FIXME: could mention usage reporting and schema reporting too, though for them should just override fetcher... + * Managed federation * Operation registry diff --git a/docs/source/schema/custom-scalars.md b/docs/source/schema/custom-scalars.md index 25af1d98988..fc98982e3be 100644 --- a/docs/source/schema/custom-scalars.md +++ b/docs/source/schema/custom-scalars.md @@ -12,7 +12,7 @@ To define a custom scalar, add it to your schema like so: scalar MyCustomScalar ``` -Object types in your schema can now contain fields of type `MyCustomScalar`. However, Apollo Server still needs to know how to interact with values of this new scalar type. +Object types in your schema can now contain fields of type `MyCustomScalar`. (FIXME: and also it can be an argument or input type field!) However, Apollo Server still needs to know how to interact with values of this new scalar type. ## Defining custom scalar logic @@ -70,13 +70,14 @@ In the example above, the `Date` scalar is represented on the backend by the `Da ### `parseValue` -The `parseValue` method converts the scalar's `serialize`d JSON value to its back-end representation before it's added to a resolver's `args`. +The `parseValue` method converts the scalar's `serialize`d JSON value to its back-end representation before it's added to a resolver's `args`. FIXME it's not really the serialized value: serialize always outputs strings, but this can be any JSON value. Apollo Server calls this method when the scalar is provided by a client as a [GraphQL variable](https://graphql.org/learn/queries/#variables) for an argument. (When a scalar is provided as a hard-coded argument in the operation string, [`parseLiteral`](#parseliteral) is called instead.) ### `parseLiteral` When an incoming query string includes the scalar as a hard-coded argument value, that value is part of the query document's abstract syntax tree (AST). Apollo Server calls the `parseLiteral` method to convert the value's AST representation (which is always a string) to the scalar's back-end representation. +(FIXME re "always a string", sorta? you can use other AST node types like int values (like you show above?), it's just that the int value node does internally store its int value as a string. but this kinda makes it sound like scalars *must* always be provided as things wrapped in double quotes?) In [the example above](#example-the-date-scalar), `parseLiteral` converts the AST value from a string to an integer, and _then_ converts from integer to `Date` to match the result of `parseValue`. @@ -163,6 +164,9 @@ server.listen().then(({ url }) => { }); ``` +FIXME: A few concerns with this example. (a) It doesn't actually run: no `Query` defined, but no indicator that this is a partial example. (b) No resolver for the oddValue field. (c) It doesn't explain what "can only contain odd integers means" and the answer is actually kinda strange: if a field would return something that is not an odd number then it just gets converted to `null` in the output, and ditto in input, but there's no errors. (d) This "null" is basically "the way we represent this non-null value in JSON"; if you have a field declared as `Odd!` (input or output) and you put an even number there, it'll happily turn it into `null` *with no error despite the `!`*! (e) You can put floating-point odd numbers and it'll work too. (f) Also you can put a string containing an odd number and it works. +I changed oddValue above to throw instead of return null, which helps with c and d. + ## Importing a third-party custom scalar If another library defines a custom scalar, you can import it and use it just like any other symbol. diff --git a/docs/source/schema/directives.md b/docs/source/schema/directives.md index abcf1001f46..f3357e52472 100644 --- a/docs/source/schema/directives.md +++ b/docs/source/schema/directives.md @@ -25,6 +25,7 @@ This example shows the `@deprecated` directive, which is a [default directive](# Each directive can only appear in _certain_ locations within a GraphQL schema or operation. These locations are listed in the directive's definition. For example, here's the GraphQL spec's definition of the `@deprecated` directive: +FIXME I believe this is now outdated as `@deprecated` can appear in more places now. ```graphql directive @deprecated( diff --git a/docs/source/schema/schema.md b/docs/source/schema/schema.md index 7d58403f840..57bd9ed2cb7 100644 --- a/docs/source/schema/schema.md +++ b/docs/source/schema/schema.md @@ -24,7 +24,7 @@ type Author { } ``` -A schema defines a collection of types and the relationships _between_ those types. In the example schema above, every `Book` has an `author`, and every `Author` has a list of `books`. By defining these type relationships in a unified schema, we enable client developers to see exactly what data is available and request a specific subset of that data with a single optimized query. +A schema defines a collection of types and the relationships _between_ those types. In the example schema above, every `Book` has an `author`, and every `Author` has a list of `books`. (FIXME: would it be better to say something like "every `Book` may have an `author`" here to indicate that these are nullable? I mean you can query `author` on any `Book` but you may not get a `Book` back. Alternatively we could add `!`s to the schema.) By defining these type relationships in a unified schema, we enable client developers to see exactly what data is available and request a specific subset of that data with a single optimized query. Note that the schema is **not** responsible for defining where data comes from or how it's stored. It is entirely implementation-agnostic. @@ -32,6 +32,8 @@ Note that the schema is **not** responsible for defining where data comes from o Every type definition in a GraphQL schema belongs to one of the following categories: +FIXME: This list is inaccurate in a few ways. (a) The Query and Mutation types *are* object types. (b) It doesn't include interface or union types. (c) If you're going to call out Query and Mutation, why not Subscription too (which has a minimal section below)? (c) This page alludes to list types in various places but doesn't actually explain anything about them (though technically they wouldn't go on the list of "kinds of types of schema definitions"). (d) similarly, non-null (`!`) isn't really explained anywhere. + * [Scalar types](#scalar-types) * [Object types](#object-types) * [The `Query` type](#the-query-type) @@ -41,6 +43,7 @@ Every type definition in a GraphQL schema belongs to one of the following catego Each of these is defined in detail below. +FIXME This is an odd placement for this call-out. We haven't mentioned the concept of "fields" in this section. Should this go under "object types"? You can monitor the performance and usage of each field within these declarations with [Apollo Studio](https://studio.apollographql.com/), providing you with data that helps inform decisions about changes to your graph. ### Scalar types @@ -59,7 +62,7 @@ These primitive types cover the majority of use cases. For more specific use cas ### Object types -Most of the types you define in a GraphQL schema are object types. An object type contains a collection of fields, each of which can be either a scalar type or _another_ object type. +Most of the types you define in a GraphQL schema are object types. An object type contains a collection of fields, each of which can be either a scalar type or _another_ object type. FIXME again, fields can also be interfaces or unions. (Also enums.) Two object types _can_ include each other as fields, as is the case in our example schema from earlier: @@ -78,6 +81,7 @@ type Author { ### The `Query` type The `Query` type defines all of the top-level **entry points** for queries that clients execute against your data graph. It resembles an [object type](#object-types), but its name is always `Query`. +FIXME It is just a normal object type, it doesn't just resemble one? Also technically its name does not have to be query; you can use the `schema {...}` declaration to provide a different name (however, in the specific case of federation we always rename to `Query` during composition). Each field of the `Query` type defines the name and return type of a different entry point. The `Query` type for our example schema might resemble the following: @@ -208,7 +212,7 @@ As with queries, our server would respond to this mutation with a result that ma } ``` -A single client request can include multiple mutations to execute. To prevent race conditions, mutations are executed serially in the order they're listed. +A single client request can include multiple mutations to execute. To prevent race conditions, mutations are executed serially in the order they're listed. FIXME I'm not sure it's normal to refer to "top-level fields of `Mutation`" as "mutations"? Maybe I'm being overly pedantic though. Also it might be nice if we had previously mentioned that fields in queries can run in parallel, so that we're contrasting against something here? Or at least mention it here? [Learn more about designing mutations](#designing-mutations) @@ -229,6 +233,7 @@ type Mutation { ``` Instead of accepting three arguments, this mutation could accept a _single_ input type that includes all of these fields. This comes in extra handy if we decide to accept an additional argument in the future, such as an `author`. +FIXME I don't think this is a really compelling argument, because in GraphQL all arguments are given by explicit name and argument order is igonred. So the analogy to JS (made in the previous paragraph) doesn't really hold up since "pass args by keyword" is how it already works! To me, the main reasons for input objects are (a) if you want to reuse a set of arguments in multiple fields or (b) if the arguments naturally have a more deeply nested structure. The example here doesn't show either of those. An input type's definition is similar to an object type's, but it uses the `input` keyword: @@ -245,6 +250,7 @@ input PostAndMediaInput { ``` Not only does this facilitate passing the `PostAndMediaInput` type around within our schema, it also provides a basis for annotating fields with descriptions that are automatically exposed by GraphQL-enabled tools: +FIXME this is also not a super compelling argument for input objects because field arguments can have descriptions (there's even an example of that below) ```graphql input PostAndMediaInput { @@ -260,6 +266,7 @@ input PostAndMediaInput { Input types can sometimes be useful when multiple operations require the exact same set of information, but you should reuse them sparingly. Operations might eventually diverge in their sets of required arguments. > **Do not use the same input type for both queries and mutations**. In many cases, arguments that are _required_ for a mutation are _optional_ for a corresponding query. +FIXME this seems unnecessarily strong? maybe more just like "think carefully about whether fields on input types should be nullable or not"? Also we haven't mentioned nullability or optionality at all yet so that seems like it might be confusing. ### Enum types @@ -341,14 +348,15 @@ Most _additive_ changes to a schema are safe and backward compatible. However, c * Removing a type or field * Renaming a type or field -* Adding nullability to a field +* Adding nullability to a field FIXME this is the first mention of nullability in all the docs, maybe it should at least have some kind of link? * Removing a field's arguments A graph management tool such as [Apollo Studio](https://studio.apollographql.com/) helps you understand whether a potential schema change will impact any of your active clients. Studio also provides field-level performance metrics, schema history tracking, and advanced security via operation safelisting. ## Documentation strings -GraphQL's schema definition language (SDL) supports markdown-enabled documentation strings. These help consumers of your data graph discover fields and learn how to use them. +FIXME These are actually called "descriptions" in the GraphQL spec (we use the term above). If we want to call them doc strings that might be OK but we should at least also use the official term? +GraphQL's schema definition language (SDL) supports Markdown-enabled documentation strings. These help consumers of your data graph discover fields and learn how to use them. The following snippet shows how to use both single-line string literals and multi-line blocks: @@ -370,7 +378,7 @@ type MyObjectType { A well-documented schema offers an enhanced development experience since GraphQL development tools (such as the [Apollo VS Code extension](https://marketplace.visualstudio.com/items?itemName=apollographql.vscode-apollo) -and GraphQL Playground) auto-complete field names along with descriptions when they're provided. Furthermore, [Apollo Studio](https://studio.apollographql.com/) displays descriptions alongside field-usage and performance details when using its metrics reporting and client-awareness features. +and GraphQL Playground) auto-complete field names along with descriptions when they're provided. (FIXME reference Explorer?) Furthermore, [Apollo Studio](https://studio.apollographql.com/) displays descriptions alongside field-usage and performance details when using its metrics reporting and client-awareness features. ## Naming conventions @@ -412,6 +420,7 @@ query EventList { ``` Because we know this is the structure of data that would be helpful for our client, that can inform the structure of our schema: +(FIXME should we have some `!` in this?) ```graphql type Query { @@ -490,7 +499,7 @@ A single mutation can modify multiple types, or multiple instances of the _same_ Additionally, mutations are much more likely than queries to cause errors, because they modify data. A mutation might even result in a _partial_ error, in which it successfully modifies one piece of data and fails to modify another. Regardless of the type of error, it's important that the error is communicated back to the client in a consistent way. -To help resolve both of these concerns, we recommend defining a `MutationResponse` interface in your schema, along with a collection of object types that _implement_ that interface (one for each of your mutations). +To help resolve both of these concerns, we recommend defining a `MutationResponse` interface in your schema, along with a collection of object types that _implement_ that interface (one for each of your mutations). FIXME this is the first reference to interfaces in the docs (other than a brief link to the interfaces page), should we at least add a link to the interfaces page? (Also the first use of non-nullable types, without explanation.) Here's what the `MutationResponse` interface looks like: diff --git a/docs/source/schema/unions-interfaces.md b/docs/source/schema/unions-interfaces.md index 8ed0fc51bbd..5c7e75b46b4 100644 --- a/docs/source/schema/unions-interfaces.md +++ b/docs/source/schema/unions-interfaces.md @@ -13,7 +13,7 @@ When you define a union type, you declare which object types are included in the union Media = Book | Movie ``` -A field can have a union as its return type. In this case, it can return any object type that's included in the union: +A field can have a union as its return type. (FIXME: but this field doesn't! it has a list of unions as its return type!) In this case, it can return any object type that's included in the union: ```graphql type Query { @@ -65,6 +65,7 @@ query GetSearchResults { ``` This query uses [inline fragments](https://graphql.org/learn/queries/#inline-fragments) to fetch a `Result`'s `title` (if it's a `Book`) or its `name` (if it's an `Author`). +FIXME it would be helpful to show what the result looks like (eg, it's not obvious that there isn't an extra level of nesting in the output corresponding to the union). Also practically speaking you almost certainly want to query `__typename` with your union, and this page doesn't mention `__typename` at all! For more information, see [Using fragments with unions and interfaces](https://www.apollographql.com/docs/react/data/fragments/#using-fragments-with-unions-and-interfaces). @@ -72,8 +73,9 @@ For more information, see [Using fragments with unions and interfaces](https://w ### Resolving a union To fully resolve a union, Apollo Server needs to specify _which_ of the union's types is being returned. To achieve this, you define a `__resolveType` function for the union in your resolver map. +FIXME If you're reading the docs in order, this is more or less the first reference to resolvers / resolver maps other than in "get started" (this schema section comes before resolvers). Not sure what I think about that. -The `__resolveType` function uses a returned object's fields to determine its type. It then returns the name of that type as a string. +The `__resolveType` function uses a returned object's fields to determine its type. (FIXME: I mean it doesn't have to use its fields? It may use something like `instanceof`?) It then returns the name of that type as a string. Here's an example `__resolveType` function for the `Result` union defined above: @@ -129,6 +131,7 @@ type Textbook implements Book { ``` A field can have an interface as its return type. In this case, it can return any object type that `implements` that interface: +FIXME (again, this doesn't have an interface as the return type, it has a list of interface) ```graphql type Query { @@ -199,6 +202,7 @@ query GetBooks { ``` This query uses [inline fragments](https://graphql.org/learn/queries/#inline-fragments) to fetch a `Book`'s `courses` (if it's a `Textbook`) or its `colors` (if it's a `ColoringBook`). +FIXME again, probably should show output, and maybe mention `__typename` For more information, see [Using fragments with unions and interfaces](https://www.apollographql.com/docs/react/data/fragments/#using-fragments-with-unions-and-interfaces). @@ -208,6 +212,7 @@ For more information, see [Using fragments with unions and interfaces](https://w [As with union types](#resolving-a-union), Apollo Server requires interfaces to define a `__resolveType` function to determine which implementing object type is being returned. Here's an example `__resolveType` function for the `Book` interface defined above: +FIXME this approach to `__resolveType` seems odd given that courses and colors are both defined as nullable. ```js{3-11} const resolvers = { diff --git a/docs/source/security/terminating-ssl.md b/docs/source/security/terminating-ssl.md index d89cd1d8451..7eadd7bb315 100644 --- a/docs/source/security/terminating-ssl.md +++ b/docs/source/security/terminating-ssl.md @@ -2,6 +2,8 @@ title: Terminating SSL --- +FIXME not super clear why we need this file rather than just linking to somebody else's docs about how to add https to an arbitrary Express app + Most production environments use a load balancer or HTTP proxy (such as nginx) to perform SSL termination on behalf of web applications in that environment. If you're using Apollo Server in an application that must perform its _own_ SSL termination, you can use the `https` module with the `apollo-server-express` [middleware