Structuring your code is probably the first task you will have to do when starting a new project. Of course, if you’re using a framework, you will probably get offered a default structure. This is because “convention over configuration” principle generally helps to get started a lot faster. However, when writing micro/small services in Node.js, I find frameworks too bloated because they come with many things that we either don’t need (ex: routing) or that we will swap with another lib at some point (ex: security). It’s also great to get to the basics to start your new app without the bloat and to handpick every dependency that we’re going to add. This way, if we’re writing multiple services with the same libraries, we come up with our own kind of framework that is perfectly suited for us. What a beautiful thing!
Apollo Server is often paired with the express web server but also include its own production-ready server (which is just a spin of Node’s http.Server). This is perfect for us, considering we can add a different web server like express or koa if we need it later. However, starting vanilla also means we are faced with no enforced patterns to structure our code. To the inexperienced developer, this can be daunting. How are we going to decide the structure of our code? Well, sweat no more, we’re going to explore three different architectures so we can pick the best one for us.
In this article, every example comes from the betaflag/graphql-server-scaffolding Github repository where we can find a working application implementing each structure.
From that repo, the only dependencies are Apollo Server and GraphQL.
npm install apollo-server graphql
This structure reflects the pure architectural simplicity of writing a server in a single file. Of course, this is unsuitable for large projects, but I can justify its usage in a micro or small service. First of all, not all APIs require a lot of code for their server.
Consider this example: you want to integrate sentiment analysis to your messages in your chat application. You decided that this shouldn’t be part of your core application and should be externalized into a separate service. It would act as a black box: a message comes in, an analysis comes out. You can start working right away with an open source library called “Sentiment” that gives you this simple interface “sentiment.analyse(message)”. Writing your sentiment analysis micro service will only require to wrap it inside a resolver and would fit nicely into a single file.
- You get an instant overview of all the code of the API
- You can get started instantly
- It’s easy to refactor and to evolve into a more structured app as you adapt your app to real-world usage.
- Only suitable to small applications otherwise it can get messy really fast
- It will probably evolve into another structure at some point
- It might be hard to collaborate with other devs if everyone’s working on the same file.
The role player
This is what you usually get when creating or scaffolding a project using a framework. It’s an easy structure to get started and fits applications of all sizes. This structure is about putting files where they belong according to their respective role. The GraphQL type definitions are found in the “typeDefs” folder, and the resolvers are located in the (you guessed it) “resolvers” folder.
Here’s an inexhaustive list of roles you can find in a GraphQL API: data, models, migrations, validators, typeDefs, resolvers, services, routes, config, utilities, etc.
│ └── index.js
│ ├── Book.js
│ └── index.js
│ ├── booksResolvers.js
│ └── index.js
Extracted from betaflag/graphql-server-scaffolding on Github
- Fit all kinds and all sizes of applications
- Newcomers can get around easily
- Each file represent a single concern like a class or an object
- As the app grows, those top-level directories become messy
- This architecture is not modular and might evolve into a monolith
- Overkill for very small apps
The domain expert
If you want to add an extra modular touch to your app, this structure is for you. In this structure, the top-level folders represent domain areas. It is modular because of the natural boundaries between these. We can even achieve extreme modularity by externalizing domains into their own NPM modules that could then be shared across applications. However, it’s not because you can that it always makes sense to do it.
Defining your domain isn’t always easy when starting a new application. Domains like users, permissions and profiles are quite natural, however, you will probably have some overlapping sub-domain at some point and this is where it gets difficult.
On the other hand, it’s great to separate your domain with a clean interface and to decide once and for all where a sub-domain lives. In large applications, teams generally form organically around domain areas. Think of data, reporting, security, etc.
│ ├── Book.js
│ ├── data.js
│ ├── index.js
│ ├── resolvers.js
│ └── typeDef.js
- Modular; a domain can be externalized in its own module
- Scalable; this structure adapt quite well to very large apps
- Clean; forcing a sub-domain into a parent make sure there is no overlap
- Also overkill for smaller apps
- Conflict can arise for establishing ownership of sub-domains (however resolving them is a really good thing)
- Domain boundaries might not be clear from the start
Choosing the right structure for you is your choice and it’s always possible to change it later. However, choosing the right one from the start is a good way to avoid future headaches.
If you know other structures that I haven’t covered, I’m looking for help to add them to my repository at betaflag/graphql-server-scaffolding. You can also add it in the comments below. Discuss with me on Twitter @betaflag