I am just a UI Engineer.
Hello from the Dev cave! 🍻
Hope you are safe and healthy.
I am a UI engineer or a Frontend developer. I am just trying to explore stuff and build cool things. I don't have enough experience or knowledge on backend.
Why I say this is because if you have worked with GraphQL and you are skilled/knowledgeable on the backend, I would love to hear your views in the comment section and anyways in which I can improve my code and application.
The Application and Tech Stack
For this post, I will be designing an application where a user can perform the following actions -
- signup
- login
- update user profile
For these actions, I will be creating 4 services. It feels like being a part of LotR -
"One Ring to rule them all,
One Ring to find them,
One Ring to bring them all
and in the darkness bind them."
😂 😂
- Auth - a service to rule them all and to handle sign up and login.
- User - a service to find them all and to handle user profile operations.
- Gateway - the gateway service to bind them all and create a super graph.
Gateway service
The simplest of all services. All it needs to do is compile all the sub graphs into a super graph, redirect the request to the correct resolver and if you want, you can also validate your user token in this service - but I haven't.
Let's install the dependencies.
- apollo-server - we will use this to create an instance of the appolo server and start it.
- @apollo-gateway - this will be used to configure our apollo gateway and also to create a connection between your federated gateway and your subgraphs.
- apollo-serve-core - we use this to enable a plugin that enables the GraphQL playground.
- dotenv - we use this to inject environment variables.
- esm - we use this as a transpiler so that we can use ES6 stuff in our code or as they say - "babel-less, bundle-less ECMAScript module loader".
- graphql - The JavaScript reference implementation for GraphQL
- nodemon - a utility to keep track of your JS files and restart the server if any changes occur.
- wait-on - gives a hint to your node server to wait for another service/server to run on another port before running this one.
Let's start building!
It will be pretty much straight forward. You can amend the implementation as per your requirements.
Create a new file - index.js - and import the required stuff.
Now, I will use RemoteGraphQLDataSource class utility to create a connection between my Gateway and all other services. The only thing I want is my Gateway to pass down the context along with the request.
All we need here is, to pass down my authorisation header from the gateway to the other connected sub graphs.
I am not sure, why they used a class for this, but this is how Apollo has provided it. It was a bummer but I couldn't find any other way. You can real more about it here.
Let's configure our gateway!
While initialising our ApolloGateway, we need to provide 2 options -
- serviceList - an array of objects where each object represents a service i.e. a subgraph apollo server.
- buildService - a function that defines how we build the connection to the subgraphs.
Let's configure our server!
While initialising the apollo server, we pass some config -
- the apollo gateway that we initialised above.
- we set subscriptions to false as - I am not sure about this - Apollo Federation does not support subscriptions as of now but either way, I haven't tried it yet. If you want to read how to configure subscriptions, check out this blog post here
- A plugins array.
- context - remember we extended the class RemoteGraphQLDataSource, to enable our connection to pass of the headers or anything sent in the request, we need to return that from here so that our class extension can access it via context.
Last thing to do is is to tell our server to listen to the port for any requests.
One last thing!
So we have setup our gateway, the connections and even the servers. But when we are running it on or local environment, I don't want to run it unless my subgraph services are running.
So here is my start script -
So what it means is wait for the http resources to become available on port 4001 and then run index.js with nodemon by using esm.
So that's it, that's your gateway!
We have our gateway setup to combine various sub graphs. Time to start building the underlying logics or the subgraphs.
The Auth service!
This service will be responsible for your user sign up and login. And the dependencies for this project are almost the same.
Also, just for your info, this is my project structure. You may create something similar or something of your own. If you have a better idea, feel free to add it in the comments.
And this is something that we be constant throughout all the services.
Create your index.js and import the dependencies.
Configure your Apollo server -
What is happening here -
- I am not initialising a new schema because this service won't run independently. Since this will be triggered by a gateway, we do this
buildFederatedSchema([{ typeDefs, resolvers, tracing: true }])
- next is configuring the context - I prefer to inject my DB modals in context so that way all the resolvers already have the access to it without me needing to import it explicitly.
Also, if we are running on local host, we need not check the x-api-key in headers. I often forget what my key is while playing with queries and mutations on the playground so this really helps.
Type Defs
I will create 3 types -
- user - describes a user.
@key(fields: "id") means that if this is ever referenced externally i.e. from another service, then it will be done through the id field.
And this Auth service will house the following operations -
I am assuming that since you are trying Apollo federation, you already are having experience with GraphQL and hence I am not deep diving into all the resolvers for Queries and Mutations. Nothing changes there. The only resolver I will be discussing here is about resolving the references.
Once this service/sub graph is contacted/connected from another service, to resolve the User reference, we need to tell our Auth service how to do it by creating a resolver for the reference.
Since this resolver will be called out by our gateway/apollo server when it needs to resolver the reference of type User, it will always have a reference object that will have that key property which we defined in our typeDef - in this case user Id. You can use that to fetch your user from the DB.
Once you have it all setup, don't forget to start your server and also add it in the service list in your gateway serve - if you already haven't.
The User service!
This service will be responsible for your user profile related operations like get user profile or update something. And the dependencies for this project are almost the same as Auth service. We just won't need stuff like bcrypt or jsonwebtoken.
Also, remember in the gateway service, we passed down the authorisation header and x-api-key. Don't forget to consume that in the context here. If user is not authorised, that is the token is not present, you must reject the request.
Also I have created a helper to check the auth token -
Talking about the main thing here - type definitions. Once you have defined a type, you cannot define it again. If you try to define your User type here again, Apollo will throw an error because it is already getting a User type from the auth service.
So to use the User type and to do you operations, you need to extend it.
Once you have extended it, you can then add more fields to it. Also, since this type is an extension of a User type coming from an another service, we tell GraphQL that it will be referenced using the Id field by adding '@external' directive in front of it.
Rest is all the same.
You can build as many services as you want for separating the concern - the only key to understand how to define and link the sub graphs.
Try building around this and add a Trip service that will have source, dest, date, and an array of members. If you do try, feel free to reach out to me in case you get stuck.
That's all folks! That's all I know around Apollo federation - defining, extending and linking schemas or types. :D
Any feedback, feel free to comment below or hit me up. Hope I helped someone in some way.
Cheers!
Top comments (0)