Mock NextJS server-side API request using MswJS
Often we don't want to make external API requests during development as well as when running thousands of automated tests.
There are many tools, such as Playwright and Cypress, that allow us to mock the client-side API requests when carrying out automated testing. But what about when developing and also what about server-side API requests? And why would we want to mock server-side API requests in the first place?
Here are a few reasons why we may want to mock these:
work offline;
not worry about 3rd party rate limits while developing;
boundary conditions for automated testing, we need different responses;
control OAuth providers when authenticating.
Don’t forget to drop a like, it really helps 🥳
Despite all these reasons and benefits, it was difficult to find examples of how to achieve mocking out server-side API requests. There were examples that were specific to a library, for example Octokit - but this would leave the project with having many different implementations of trying to solve the same problem for each library being used.
Is there a way I can intercept all server-side RESTful API requests without my app knowing so it thinks it is real?
TL;DR
Yes it is possible! I will create an example NextJS app, that queries GitHub’s API on the server-side for real data. Then I will introduce the library MswJS that will allow me to create a JSON file to return a customised response for the GitHub API request. Yes my app will think it is making a real API request to GitHub’s API.
You might be surprised to hear there isn’t much code required to make this happen, there is actually more JSON as part of the response. But there is a small tricky part, which is integrating the MswJS code with the NextJS project. This is possible with an experimental feature in NextJS called “instrumentation”, which I will talk about later in this blog.
NextJS
Firstly I will create a standard NextJS app using the npx create command:
npx create-next-app@latest
I answer the CLI prompt questions with the type of NextJS project I wish to create. For example `javascript/typescript`.
Then I navigate into the new project directory and start the application in dev mode with the NextJS command:
npm run dev
Then I visit `http://localhost:3000` to see the web app.
GitHub Repo API
The next step is to make a server-side 3rd party API request. I will use GitHub’s API , but you can use any other API you prefer.
For the GitHub API request, we can make a request using NodeJS’s fetch or a library like Octokit, as both will be making the same http RESTful API call to GitHub.
To save installing an npm dependency, l will use NodeJS’s fetch.
I start by removing all the generated example content in `src/app/page.js` and make the fetch request to GitHub’s API with:
const res = await fetch("https://api.github.com/users/eddiejaoude");
const data = await res.json();
The file now looks like this, with some data displayed with help from GitLab Duo autocomplete:
export default async function Home() {
const res = await fetch("https://api.github.com/users/eddiejaoude");
const data = await res.json();
console.log(data);
return (
<ul>
<li>Name: {data.name}</li>
<li>Location: {data.location}</li>
<li>Bio: {data.bio}</li>
</ul>
);
}
As I still don’t like the look of the output, I asked GitLab Duo to refactor the output by highlighting the `return` section and asking GitLab Duo Chat to refactor using Tailwind:
That immediately looks a lot better:
Above I am making a real API request, but what if:
We don’t have internet?
We are running automated tests and we don’t want to hit the API rate limit of GitHub? or
We want to test different results from GitHub, no data, lots of data etc?
This is where the library MswJS comes in to save the day!
MswJS
Mock Service Worker (MSW) is an API mocking library for browser and NodeJS. With MSW, you can intercept outgoing requests, observe them, and respond to them using mocked responses.
Installing MswJS
Firstly, I run the npm command:
npm install msw@latest --save-dev
Then I create the folder `mocks` in the folder `src` (path will be `src/mocks`).
This is followed by creating a file `handlers.js` in the new mocks folder. In this file I list all the requests I want to intercept and the response I want to return. Later on these can be split out into separate files as it grows.
Here is a simplified example:
http.get(
"https://api.github.com/users/eddiejaoude",
({ request, params, cookies }) => HttpResponse.json(user)
),
This will watch for a GET request to the url https://api.github.com/users/eddiejaoude and will return a json response of my choice. We are able to use wildcards and regular expressions (regex) - for example https://api.github.com/users/* - these would watch and listen for any user to the GitHub API url and always return the same response. However, we could have a smarter function that manipulated the data, but for this example it is not needed.
The whole contents of the `handlers.js` file will be:
import { http, HttpResponse } from "msw";
import user from "./user";
export const handlers = [
http.get(
"https://api.github.com/users/eddiejaoude",
({ request, params, cookies }) => HttpResponse.json(user)
),
];
It is possible to mock the status codes and headers also. The full official documentation can be found here https://mswjs.io/docs/basics/mocking-responses.
Next I need to create a JSON file for the data I want to respond and I can edit this manually. To get the JSON file contents, I visited the url https://api.github.com/users/eddiejaoude in my browser and saved it to a new directory `data` in the `mocks` directory (full path `src/mocks/data`).
If you are following along, here is the contents for the JSON file:
{
"login": "eddiejaoude",
"id": 624760,
"node_id": "MDQ6VXNlcjYyNDc2MA==",
"avatar_url": "https://avatars.githubusercontent.com/u/624760?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/eddiejaoude",
"html_url": "https://github.com/eddiejaoude",
"followers_url": "https://api.github.com/users/eddiejaoude/followers",
"following_url": "https://api.github.com/users/eddiejaoude/following{/other_user}",
"gists_url": "https://api.github.com/users/eddiejaoude/gists{/gist_id}",
"starred_url": "https://api.github.com/users/eddiejaoude/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/eddiejaoude/subscriptions",
"organizations_url": "https://api.github.com/users/eddiejaoude/orgs",
"repos_url": "https://api.github.com/users/eddiejaoude/repos",
"events_url": "https://api.github.com/users/eddiejaoude/events{/privacy}",
"received_events_url": "https://api.github.com/users/eddiejaoude/received_events",
"type": "User",
"site_admin": false,
"name": "Eddie Jaoude",
"company": "Open Source Engineer",
"blog": "https://eddiejaoude.substack.com/p/links",
"location": "London, UK",
"email": null,
"hireable": true,
"bio": "Open Source DevRel!\r\nGitHub Star ⭐ program + GitHub Star of the Year 🤓 + GitHub Accelerator with BioDrop (aka LinkFree)... let's GEEK out together!",
"twitter_username": "eddiejaoude",
"public_repos": 268,
"public_gists": 24,
"followers": 8251,
"following": 67,
"created_at": "2011-02-18T06:56:22Z",
"updated_at": "2024-08-10T16:36:56Z"
}
The last file I need to create is for node (if you want this to work in the browser, you will have to add an additional file). Create the file `node.js` in the path `src/mocks` and the contents will be:
import { setupServer } from 'msw/node'
import { handlers } from './handlers'
export const server = setupServer(...handlers)
I then need to enable MswJS in our NextJS app with the code:
import { server } from './mocks/node'
server.listen()
Don’t worry we will cover how to do this in the next section.
NextJS’s experimental feature Instrumentation
After researching I found the best recommended way to integrate MswJS into an AppRouter NextJS app is with the `instrumentation.js` file. If you have not heard of before, don’t worry, neither had I! This file is a new experimental feature that needs enabling in order to be used.
Instrumentation is the process of using code to integrate monitoring and logging tools into your application. This allows you to track the performance and behavior of your application, and to debug issues in production.
In the `next.config.js` file I enabled the feature with:
module.exports = {
experimental: {
instrumentationHook: true,
},
}
In the `instrumentation.js` file, I can start the mock server as follows:
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
const { mockServer } = await import("./mocks/node");
mockServer.listen();
}
}
Note that we check the enviroment is “nodejs” then we import the mock server. Otherwise the app will throw an error if not in the correct runtime.
The next step is to restart NextJS to get the same data displayed in the browser. So how do we know it’s working? The easiest way is to go and change some data in the JSON file.
To illustrate this, I have changed the “name” field from “Eddie Jaoude” to “Eddie Jaoude (mock)”, and this is the result:
Next Steps
Now you have full control to intercept and mock out everything you need in your app! But one more change to the `instrumentation.js` file is needed, as when you deploy you will not want to use mock data but instead real data.
After looking into this, I learnt that the best way is to use an additional environment variable `APP_ENV` and add it to the condition in the `intrumentation.js` file, resulting in:
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs" && process.env.APP_ENV === "test") {
const { mockServer } = await import("./mocks/node");
mockServer.listen();
}
}
This way the mocking server can be enabled for local development and also automated testing.
Don’t forget to add `APP_ENV` to your `.env` and `.env.example` files
# APP_ENV=test
Conclusion
We can setup our projects for success and make our development workflow better because we have great Open Source tools available to us. However with so many tools, it is impossible to know them all, let alone try them all out. I hope this post will help you become aware and understand the best way to achieve server-side API mocking with NextJS, so you can use this functionality in your projects.
I will have more blog posts and YouTube videos on this topic coming out soon. I will be covering areas such as how we can use what we just learnt to mock out authentication that is using an OAuth provider like GitHub OAuth.
See the example project here https://github.com/eddiejaoude/nextjs-mswjs
If you would prefer to watch a video on this subject:
Very well explained!
Good that you are using fetching with 'await' instead of multi 'then' :)