Updating the code
We've got the packages we need, so let's start swapping them in. A good place to start is src/index.js
- where our AS3 server code is defined!
const { ApolloServer } = require("apollo-server");const typeDefs = require("./schema");const resolvers = require("./resolvers");const TrackAPI = require("./datasources/track-api");async function startApolloServer(typeDefs, resolvers) {const server = new ApolloServer({typeDefs,resolvers,dataSources: () => {return {trackAPI: new TrackAPI(),};},});const { url, port } = await server.listen();console.log(`🚀 Server is running🔉 Listening on port ${port}📠Query at ${url}`);}startApolloServer(typeDefs, resolvers);
New imports
First, let's update the line requiring in ApolloServer
so that it uses our new @apollo/server
package.
- const { ApolloServer } = require("apollo-server");+ const { ApolloServer } = require("@apollo/server")
As part of the AS4 upgrade, the @apollo/server
package consists of a number of new, smaller packages, and we can access those utilities and functions to customize how our server runs. We'll still use the ApolloServer
class we've required, but we need an additional function - startStandaloneServer
.
We can access startStandaloneServer
through a new import, which targets the /standalone
directory within the @apollo/server
package.
const { ApolloServer } = require("@apollo/server");const { startStandaloneServer } = require("@apollo/server/standalone");const typeDefs = require("./schema");// other imports...
Configuring our server
startStandaloneServer
To integrate the startStandaloneServer
function with our existing server implementation, we need to make a few updates.
The first line we can remove is our server.listen()
call. We'll no longer listen to our server instance directly, but rather use our startStandaloneServer
function instead.
// ... importsasync function startApolloServer(typeDefs, resolvers) {const server = new ApolloServer({typeDefs,resolvers,dataSources: () => {return {trackAPI: new TrackAPI(),};},});- const { url, port } = await server.listen();console.log(`🚀 Server is running🔉 Listening on port ${port}📠Query at ${url}`);}startApolloServer(typeDefs, resolvers);
In its place, we can await
the results of our startStandaloneServer
call, passing it our initialized server
constant.
// ... importsasync function startApolloServer(typeDefs, resolvers) {const server = new ApolloServer({typeDefs,resolvers,dataSources: () => {return {trackAPI: new TrackAPI(),};},});await startStandaloneServer(server);console.log(`🚀 Server is running🔉 Listening on port ${port}📠Query at ${url}`);}startApolloServer(typeDefs, resolvers);
From the results of this call, we can pull out the url
that our server is running on.
const { url } = await startStandaloneServer(server);
The context
function
startStandaloneServer
can take a second argument, which is a configuration object that lets us customize some of our server's features. Let's go ahead and add the curly braces for that configuration object.
const { url } = await startStandaloneServer(server, {// configuration details});
Previously, we defined dataSources
directly in our ApolloServer
class constructor. The data we returned from that function became available to our resolvers on their third positional argument, context
.
In AS4, that definition moves to the startStandaloneServer
configuration object under a key called context
, which is an asynchronous function.
Let's add that key now:
const { url } = await startStandaloneServer(server, {context: async () => {},});
Within this function, we'll return an object. This is the object that our resolvers receive on their third argument.
const { url } = await startStandaloneServer(server, {context: async () => {return {};},});
Note: In AS3, we referred to the third positional argument that resolver functions receive as context
. As a matter of convention in AS4, we now refer to that argument as contextValue
. This is because when we call the context
function in the server configuration, the object it returns arrives on our resolvers' third positional argument.
Now, let's remove the dataSources
definition defined in our ApolloServer
instantiation. Instead, we can define a dataSources
key in the object returned by the context
function. We'll set dataSources
to an object as well.
// ... importsconst server = new ApolloServer({typeDefs,resolvers,- dataSources: () => {- return {- trackAPI: new TrackAPI(),- };- },});const { url } = await startStandaloneServer(server, {context: async () => {return {+ dataSources: {}}}})
Finally, we can move the trackAPI
key, and the instantiation of the TrackAPI
class, to the dataSources
object.
const { url } = await startStandaloneServer(server, {context: async () => {return {dataSources: {trackAPI: new TrackAPI(),},};},});
Note: In AS3, dataSources
was defined as a function that returned an object containing the various data sources we wanted our resolvers to have access to. In AS4, dataSources
is set to an object containing these data source properties instead. Make sure that your dataSources
object matches the implementation above!
Hooking up the cache
When we used Apollo Server 3, an initialize
function was called for each DataSource
class created, passing in the Apollo Server's cache
and context
to make them available for use. In Apollo Server 4, we'll handle this ourselves. We don't need our TrackAPI
class to have access to any additional context
information (like a user authentication token), so instead we'll make sure we're passing it just the cache
from our server initialization.
First, we need to get access to the cache
. In AS4, this is made available as a read-only variable on our server. Let's start by destructuring our server
to get access to cache
.
const { url } = await startStandaloneServer(server, {context: async () => {const { cache } = server;return {dataSources: {trackAPI: new TrackAPI(),},};},});
Next, we'll update our instantiation of the TrackAPI
class, passing it an object that contains cache
.
const { url } = await startStandaloneServer(server, {context: async () => {const { cache } = server;return {dataSources: {trackAPI: new TrackAPI({ cache }),},};},});
Excellent! That's our data source configured in AS4! Our track data will now be available to our resolver functions.
Clean up
Here's the current state of our server:
const { ApolloServer } = require("@apollo/server");const { startStandaloneServer } = require("@apollo/server/standalone");const typeDefs = require("./schema");const resolvers = require("./resolvers");const TrackAPI = require("./datasources/track-api");async function startApolloServer(typeDefs, resolvers) {const server = new ApolloServer({ typeDefs, resolvers });const { url } = await startStandaloneServer(server, {context: async () => {const { cache } = server;return {dataSources: {trackAPI: new TrackAPI({ cache }),},};},});console.log(`🚀 Server is running🔉 Listening on port ${port}📠Query at ${url}`);}startApolloServer(typeDefs, resolvers);
We've done the bulk of the code updates, but there's another tweak we need to make. Right now, our console.log
statement is looking for a port
variable. As you can see, we're no longer pulling out this value from our server.
Let's instead define a port
variable at the top of our file, and future-proof our implementation to read from process.env.PORT
if the environment our server is running in sets one by default. If no PORT
variable exists, we can set it to a default value of 4000
.
const port = process.env.PORT || 4000;
We can ask our server to read in this port
value by defining a listen
key inside the startStandaloneServer
config object.
As the value of the listen
, we'll set an object with a port
key. And because the name of the key matches the name of the value we want to pass in, we can use the ES6 shorthand syntax and simply set both the key and the value at once with port
.
+ const port = process.env.PORT || 4000;async function startApolloServer(typeDefs, resolvers) {const server = new ApolloServer({ typeDefs, resolvers });const { url } = await startStandaloneServer(server, {context: async () => {const { cache } = server;return {dataSources: {trackAPI: new TrackAPI({ cache }),},};},+ listen: { port },});}
That's it for the server updates... but if you run npm start
in your terminal, you'll still see some errors! Other parts of our app are still using the older packages from AS3. Let's tackle those next.
Share your questions and comments about this lesson
Your feedback helps us improve! If you're stuck or confused, let us know and we'll help you out. All comments are public and must follow the Apollo Code of Conduct. Note that comments that have been resolved or addressed may be removed.
You'll need a GitHub account to post below. Don't have one? Post in our Odyssey forum instead.