<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Mark Roberts</title>
    <description>The latest articles on Forem by Mark Roberts (@markandrus).</description>
    <link>https://forem.com/markandrus</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1028202%2Ffd901013-76fd-4068-9897-c47ff875fa0f.jpeg</url>
      <title>Forem: Mark Roberts</title>
      <link>https://forem.com/markandrus</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/markandrus"/>
    <language>en</language>
    <item>
      <title>Deployment Notes: How we document changes to production</title>
      <dc:creator>Mark Roberts</dc:creator>
      <pubDate>Wed, 17 May 2023 05:00:00 +0000</pubDate>
      <link>https://forem.com/propel/deployment-notes-how-we-document-changes-to-production-223b</link>
      <guid>https://forem.com/propel/deployment-notes-how-we-document-changes-to-production-223b</guid>
      <description>&lt;p&gt;At &lt;a href="https://www.propeldata.com/"&gt;Propel&lt;/a&gt;, our continuous integration (CI) and continuous delivery (CD) processes enables us to deliver features rapidly, multiple times during a single workday. With each release to production, we’ve found it useful to document&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;customer-facing changes contained within the release,&lt;/li&gt;
&lt;li&gt;non-customer-facing changes within the release, and&lt;/li&gt;
&lt;li&gt;our strategy for rolling back the release.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We call this documentation “deployment notes” or “release notes” and we publish these notes on every release to an internal Slack channel. For example, you can see a screenshot of deployment notes for one of our services below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--t6-ATK47--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bs1sznjhbulituty0b7f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--t6-ATK47--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bs1sznjhbulituty0b7f.png" alt="A screenshot of deployment notes being posted to our internal Slack channel." width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why do we do this?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Customer communication
&lt;/h3&gt;

&lt;p&gt;For changes that are not gated behind a feature flag, the deployment notes provide a record of when the changes were actually shipped to production, and for customers, this is what matters. Even with continuous delivery, there will be a little bit of delay between merging to main and releasing to production. So as soon as the deployment notes go out, we know we can reach out to customers and let them know that a new feature or bug fix is available.&lt;/p&gt;

&lt;p&gt;Additionally, we often refer to deployment notes when we put together our &lt;a href="https://www.propeldata.com/docs/changelog"&gt;customer-facing changelog entries&lt;/a&gt; for the month. The deployment notes are the record of truth, so we don’t have to ask engineers, “Did feature X ship already?” We just go to our internal Slack channel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Internal communication
&lt;/h3&gt;

&lt;p&gt;Occasionally, larger changes will require multiple deployments of one or more services. These changes may not be immediately customer-facing; however, we still want to document and communicate where we are in the process, so that there’s never any doubt about the state of production.&lt;/p&gt;

&lt;p&gt;For example, Propel’s GraphQL API is actually a GraphQL supergraph combining multiple subgraphs using Apollo Router. In order to extend our API with a new subgraph X, we would&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deploy a new service for subgraph X.&lt;/li&gt;
&lt;li&gt;Update and deploy Apollo Router to include subgraph X.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The deployment in step 1 contains no customer-facing changes; however, we still write the deployment notes. The deployment notes serve as a record that step 1 was completed, allowing us to continue to step 2.&lt;/p&gt;

&lt;p&gt;Externalizing this knowledge means no single engineer has to keep the state of production in their head and everyone can see what was deployed. And that can be really freeing when engineers go on vacation or when collaborating with teammates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Incident response
&lt;/h3&gt;

&lt;p&gt;Bugs are a fact of life, and the only way to eliminate them is to stop writing software. But we’re not in the business of &lt;em&gt;not writing software&lt;/em&gt;. We’re in the business of shipping features that delight customers! That’s why we invest so heavily in our testing and continuous integration processes, in order to mitigate the likelihood of shipping unintended behavior changes to production.&lt;/p&gt;

&lt;p&gt;Despite this, there &lt;em&gt;will be&lt;/em&gt; behavior changes, bugs, and even serious incidents that automated testing doesn’t catch. When this happens, it’s the responsibility of an on-call engineer to remediate the issue as quickly as possible in order to restore the customer experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rollbacks
&lt;/h3&gt;

&lt;p&gt;Most of the time, pagers go off and incidents are discovered shortly after a deployment, and the quickest path to remediation is a rollback. By “rollback”, we mean rolling back to the previous known working version of the software. This is such a common remediation strategy that we emphasize it in our deployment notes. The instructions we usually give are&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Re-deploy the last successful release.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Of course, there are some rollbacks that could be more complicated than a re-deploy, like a schema change; but, as a rule, we try design all of our changes to be easy to roll back.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reconstructing timelines
&lt;/h3&gt;

&lt;p&gt;Sometimes a bug or behavior change is discovered which does not correspond to a recent deployment. In these cases, accurate deployment notes have been essential for tracking down the source of the change and reconstructing the incident timeline. Having this clarity allows us to fix issues and resolve incidents with confidence. This is a crucial part of our incident response process that we will describe in a future blog post.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do we do this?
&lt;/h2&gt;

&lt;p&gt;Deployment notes are a core feature of our CI/CD pipelines. Once we merge a change to main, our pipeline deploys the change to our development environment, where it runs our cluster tests. If the cluster tests succeed, it posts to Slack requesting deployment notes and approval for deploying to production.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---Jq8Ef04--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2v1va862wsk80g2iv0i8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---Jq8Ef04--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2v1va862wsk80g2iv0i8.png" alt="Our pipeline posts to Slack, requesting deployment notes and approval for deploying to production." width="800" height="112"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--s_JWk4bK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6ld4wl826fikwowamlpa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--s_JWk4bK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6ld4wl826fikwowamlpa.png" alt="In GitHub, we include our deployment notes as a comment when approving." width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our pipelines are currently running as GitHub Actions and using &lt;a href="https://docs.github.com/en/actions/managing-workflow-runs/reviewing-deployments"&gt;GitHub environments&lt;/a&gt;, but they used to run on CircleCI, so you could build a similar solution on any provider.&lt;/p&gt;

&lt;h3&gt;
  
  
  Curation vs. Automation
&lt;/h3&gt;

&lt;p&gt;There are many tools that can automate the creation of changelogs and release notes. For example, we follow the &lt;a href="https://www.conventionalcommits.org/en/v1.0.0/"&gt;Conventional Commits&lt;/a&gt; specification, which means we prefix our commit messages with “fix” or “feat” to indicate a bug fix or new feature, and we include “BREAKING CHANGE” if we need to ship a breaking change.&lt;/p&gt;

&lt;p&gt;To be clear, we consider Conventional Commits a best practice! We even automatically bump version numbers according to the commit history. But we’ve found the automated changelogs derived from Conventional Commits to be too low-level for use in deployment notes. And when it’s handled automatically, we’re not necessarily “thinking” about the changes going out.&lt;/p&gt;

&lt;p&gt;That’s why, on every release, we have the engineer in charge of the release pause and write the deployment notes. This allows the engineer to communicate at a higher level the changes going out and to review once more the changes being released.&lt;/p&gt;

&lt;h3&gt;
  
  
  Template
&lt;/h3&gt;

&lt;p&gt;We always follow the same Markdown template, which you are free to re-use for yourself. If one of the sections is not applicable for a particular deployment, we write “N/A”.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Customer-facing changes

- Cool new feature X
- Cool new feature Y

# Non-customer-facing changes

- Refactored Z

# Rollback strategy

Re-deploy the last successful release.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Deployment notes are an effective tool for communicating customer-facing and non-customer-facing changes. They’re also useful for incident response, where the on-call engineer needs to know how to rollback changes. Although tools exist to automate the creation of changelogs, we’ve found it best to explicitly author our deployment notes and require human approval before releasing them to production.&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>documentation</category>
      <category>githubactions</category>
      <category>deploy</category>
    </item>
    <item>
      <title>How we migrated to Apollo Server 4</title>
      <dc:creator>Mark Roberts</dc:creator>
      <pubDate>Wed, 22 Feb 2023 16:44:00 +0000</pubDate>
      <link>https://forem.com/propel/how-we-migrated-to-apollo-server-4-3m06</link>
      <guid>https://forem.com/propel/how-we-migrated-to-apollo-server-4-3m06</guid>
      <description>&lt;p&gt;At &lt;a href="https://www.propeldata.com/" rel="noopener noreferrer"&gt;Propel&lt;/a&gt;, it’s no secret we’re fans of GraphQL: our entire product is centered around serving in-product analytics and customer dashboards using this flexible and performant technology.&lt;/p&gt;

&lt;p&gt;We chose GraphQL first because it empowers clients to fetch exactly the data they need, usually in a single round-trip to the server. This is great for building customer dashboards, which may include multiple time series, counters, and leaderboards in a single page. It’s part of what makes using Propel so snappy.&lt;/p&gt;

&lt;p&gt;Second, the developer experience around GraphQL is amazing, and we’ve been fortunate to use some great tools from &lt;a href="https://the-guild.dev/" rel="noopener noreferrer"&gt;The Guild&lt;/a&gt; and &lt;a href="https://www.apollographql.com/" rel="noopener noreferrer"&gt;Apollo&lt;/a&gt; in building our product. For example, we publish our GraphQL schemas to &lt;a href="https://studio.apollographql.com/public/Propel-API/home?variant=production" rel="noopener noreferrer"&gt;Apollo Studio&lt;/a&gt;, we embed the Apollo Studio Explorer in &lt;a href="https://www.propeldata.com/docs" rel="noopener noreferrer"&gt;our docs&lt;/a&gt;, and our GraphQL API is actually built on top of &lt;a href="https://www.apollographql.com/docs/apollo-server/" rel="noopener noreferrer"&gt;Apollo Server&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I want to take a moment to share part of our journey on the server-side, since we just recently upgraded our GraphQL APIs from Apollo Server 3 to 4. Apollo did a great job documenting the expected effort in their “&lt;a href="https://www.apollographql.com/docs/apollo-server/migration/" rel="noopener noreferrer"&gt;Migrating to Apollo Server 4&lt;/a&gt;” guide, but we still encountered (and solved!) some unexpected differences in Apollo Server 4.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Apollo Server 3 is officially deprecated and will reach end-of-life October 22, 2023. Read along to see how we at Propel migrated to Apollo Server 4.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Basic changes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The apollo-server library is now &lt;a class="mentioned-user" href="https://dev.to/apollo"&gt;@apollo&lt;/a&gt;/server
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;In Apollo Server 3&lt;/strong&gt;, you depended on version 3.x of the apollo-server library and any supporting libraries, like apollo-server-errors or apollo-server-core.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In Apollo Server 4&lt;/strong&gt;, everything is published in a single, namespaced library. You only have to depend on version 4.x of &lt;a class="mentioned-user" href="https://dev.to/apollo"&gt;@apollo&lt;/a&gt;/server.&lt;/p&gt;

&lt;h3&gt;
  
  
  New &lt;code&gt;startStandaloneServer&lt;/code&gt; method
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;In Apollo Server 3&lt;/strong&gt;, the apollo-server library wrapped apollo-server-express and offered a “batteries-included” HTTP server for handling GraphQL requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In Apollo Server 4&lt;/strong&gt;, the “batteries-included” approach is now &lt;code&gt;startStandaloneServer&lt;/code&gt;. Migrating to this new method is straightforward and well-described in the migration guide. At Propel, we tried to use this new method; however, for reasons described below, we ultimately needed to use &lt;code&gt;expressMiddleware&lt;/code&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;gql&lt;/code&gt; template literal tag has been removed
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;In Apollo Server 3&lt;/strong&gt;, you could import the &lt;code&gt;gql&lt;/code&gt; template literal tag directly from the apollo-server library. This template literal tag is provided by &lt;a href="https://github.com/apollographql/graphql-tag" rel="noopener noreferrer"&gt;the graphql-tag library&lt;/a&gt; and allows parsing a GraphQL query string to an AST that can be used by Apollo and other GraphQL libraries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;gql&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apollo-server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In Apollo Server 4&lt;/strong&gt;, this template literal tag is no longer exported. In fact, it’s no longer depended on by the &lt;a class="mentioned-user" href="https://dev.to/apollo"&gt;@apollo&lt;/a&gt;/server library at all. At Propel, we had a few usages of &lt;code&gt;gql&lt;/code&gt; in our backend, so we had to add a dependency on graphql-tag and change our import in order to fix this. Easy fix. 👍&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;gql&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;graphql-tag&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ApolloError is replaced by GraphQLError
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;In Apollo Server 3&lt;/strong&gt;, the apollo-server-errors library defined an ApolloError class. At Propel, we sub-classed ApolloError in our backend to create a hierarchy of errors. Any time we needed to throw an error from a resolver and have it appear in the GraphQL response’s &lt;code&gt;errors&lt;/code&gt; array, we would use this class. For example, one of our NotFoundErrors looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ApolloError&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apollo-server-errors&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotFoundError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ApolloError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NotFoundError&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NOT_FOUND&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

  &lt;span class="nf"&gt;constructor &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resourceName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;resourceName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; not found`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Members omitted…&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In Apollo Server 4&lt;/strong&gt;, ApolloError is removed. Instead, we should now use the GraphQLError class from the graphql library. We already had a dependency on graphql, so we just needed to update our sub-classes. After migrating, our NotFoundError looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GraphQLError&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;graphql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotFoundError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;GraphQLError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NotFoundError&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NOT_FOUND&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

  &lt;span class="nf"&gt;constructor &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resourceName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;resourceName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; not found`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Members omitted…&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additionally, Apollo Server 4 adds the ability to control HTTP response status codes by including an &lt;code&gt;http&lt;/code&gt; extension in the GraphQLError. For example, throwing a GraphQLError like the following will cause the HTTP server to respond with &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418" rel="noopener noreferrer"&gt;418 I’m a teapot&lt;/a&gt;. 🫖&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GraphQLError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I'm a teapot&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TEAPOT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;418&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Plugin changes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Plugin error-handling has changed
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;In Apollo Server 3&lt;/strong&gt;, plugins could receive the incoming request object in their &lt;code&gt;requestDidStart&lt;/code&gt; method. At Propel, we used this to build an authentication and authorization plugin (our “AuthPlugin”).&lt;/p&gt;

&lt;p&gt;Our AuthPlugin determines if incoming requests are authentic and if they are authorized. If an incoming request is inauthentic or unauthorized, we throw an HttpQueryError with status code 403. This would cause Apollo Server to return a 403 Unauthorized HTTP response back to the user. It looked something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ApolloServer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apollo-server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;HttpQueryError&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apollo-server-core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthPlugin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;requestDidStart &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isAuthenticated&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isAuthorized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HttpQueryError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unauthorized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authCtx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;authCtx&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApolloServer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;typeDefs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AuthPlugin&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In Apollo Server 4&lt;/strong&gt;, not only is HttpQueryError removed, but throwing an error from a plugin’s &lt;code&gt;requestDidStart&lt;/code&gt; method results in a 500 Internal Server Error. We already knew we would need to migrate from HttpQueryError to GraphQLError, but how could we control the HTTP status code if Apollo Server was re-throwing plugin errors as 500s?&lt;/p&gt;

&lt;p&gt;After some head-scratching, I opened &lt;a href="https://github.com/apollographql/apollo-server/issues/7278" rel="noopener noreferrer"&gt;an issue&lt;/a&gt; on Apollo Server’s GitHub repository. There, Apollo Server contributor &lt;strong&gt;@​glasser&lt;/strong&gt; shared a helpful suggestion: why not invoke our AuthPlugin from Apollo Server’s &lt;code&gt;context&lt;/code&gt; function? Throwing from &lt;code&gt;context&lt;/code&gt; would ensure we can control the HTTP status response without having to introduce more methods and error checks to our AuthPlugin (like &lt;code&gt;unexpectedErrorProcessingRequest&lt;/code&gt;). With that suggestion in mind, we rewrote our AuthPlugin as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ApolloServer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@apollo/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;startStandaloneServer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@apollo/server/standalone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GraphQLError&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;graphql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthPlugin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;getAuthContext &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// ← Method renamed (not a true plugin anymore).&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isAuthenticated&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isAuthorized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GraphQLError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unauthorized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UNAUTHORIZED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;authCtx&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApolloServer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;typeDefs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="c1"&gt;// ← AuthPlugin is omitted here.&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authPlugin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AuthPlugin&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;startStandaloneServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Plugin invoked inside of `context`. Read on for details.&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;authCtx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;authPlugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAuthContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s a few extra lines, but it works well. 👍&lt;/p&gt;

&lt;h3&gt;
  
  
  Plugins no longer receive the request URL
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;In Apollo Server 3&lt;/strong&gt;, the incoming request object passed to &lt;code&gt;requestDidStart&lt;/code&gt; included a URL property. Our AuthPlugin would read the request’s URL and headers to determine if the request was authentic and authorized.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In Apollo Server 4&lt;/strong&gt;, this URL property has been removed. I was curious about this change, so &lt;a href="https://github.com/apollographql/apollo-server/issues/7277" rel="noopener noreferrer"&gt;I asked about it&lt;/a&gt; on Apollo Server’s GitHub repository. Apollo Server contributor &lt;strong&gt;@​glasser&lt;/strong&gt; helpfully pointed out that the URL was still accessible in Apollo Server’s &lt;code&gt;context&lt;/code&gt; function. Because we had already moved our AuthPlugin inside of &lt;code&gt;context&lt;/code&gt;, we could accept the request object provided by &lt;code&gt;startStandaloneServer&lt;/code&gt; and extract the URL from there. The fix was straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;startStandaloneServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// `req` here is an `http.IncomingMessage` with `url`.&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;authCtx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;authPlugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestDidStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Server changes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  HTTP-level health check endpoint removed
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;In Apollo Server 3&lt;/strong&gt;, there was an HTTP-level health check endpoint included at &lt;code&gt;/.well-known/apollo/server-health&lt;/code&gt;. This would return 200 OK for any GET request. While too simple to evaluate if the server process was truly healthy, it did indicate whether the Node.js process and HTTP server were running. At Propel, we deploy our GraphQL API servers as Fargate tasks behind Application Load Balancers (ALBs), so this was a natural choice to configure as our target groups’ health check endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;https://your.server/.well-known/apollo/server-health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In Apollo Server 4&lt;/strong&gt;, the HTTP-level health check endpoint has been removed. Apollo Server recommends issuing a simple GraphQL query instead. For example,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;https://your.server/?query&lt;span class="o"&gt;=&lt;/span&gt;%7B__typename%7D
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Indeed, this is a more sophisticated health check than the HTTP-level health check in Apollo Server 3: by executing a GraphQL query, we can ensure that Apollo Server is working in addition to the Node.js process and HTTP server.&lt;/p&gt;

&lt;p&gt;At Propel, two issues prevented us from adopting this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Our entire GraphQL API is protected by our AuthPlugin, so by default the ALBs’ health check requests would be rejected with status code 403.&lt;/li&gt;
&lt;li&gt;Apollo Server’s CSRF protection would require the ALB to send an “Apollo-Require-Preflight” header with each health check request, which is unsupported.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead, we followed Apollo Server’s “&lt;a href="https://www.apollographql.com/docs/apollo-server/api/standalone#swapping-to-expressmiddleware" rel="noopener noreferrer"&gt;Swapping to &lt;code&gt;expressMiddleware&lt;/code&gt;&lt;/a&gt;” guide. By swapping to &lt;code&gt;expressMiddleware&lt;/code&gt;, we would get access to the underlying &lt;code&gt;express&lt;/code&gt; HTTP server. This way, we can install a health check endpoint at a new URL, &lt;code&gt;/.well-known/server-health&lt;/code&gt;. This health check endpoint is completely private, and so it’s safe to leave unauthenticated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// Setup Apollo Server with `expressMiddleware`…&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/.well-known/server-health&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Response follows https://tools.ietf.org/html/draft-inadarei-api-health-check-01&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/health+json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pass&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We followed the same response format as Apollo Server 3. Indeed, this is a very simple check; however, we plan to incorporate more checks in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Migrating from Apollo Server 3 to Apollo Server 4 took us less than a week, and we received a lot of help from Apollo’s own migration guide as well as on Apollo Server’s GitHub issues tracker. If any of this interests you — be it GraphQL or in-product analytics — reach out, follow us on Twitter &lt;a href="https://twitter.com/@propeldatacloud" rel="noopener noreferrer"&gt;@PropelDataCloud&lt;/a&gt;, or &lt;a href="https://www.propeldata.com/newsletter?__hstc=17958374.7add73943c8f554c1c478c947e376657.1661881287613.1667148400062.1667167207872.21&amp;amp;__hssc=17958374.2.1667167207872&amp;amp;__hsfp=2429260968" rel="noopener noreferrer"&gt;subscribe to our email newsletter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you’d like to learn more about our GraphQL API, &lt;a href="https://www.propeldata.com/docs/api/about-the-graphql-api" rel="noopener noreferrer"&gt;check out our GraphQL API documentation&lt;/a&gt; or &lt;a href="https://www.propeldata.com/request-access" rel="noopener noreferrer"&gt;request access&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>devmemes</category>
      <category>security</category>
    </item>
  </channel>
</rss>
