{"id":318115,"date":"2020-08-10T07:32:47","date_gmt":"2020-08-10T14:32:47","guid":{"rendered":"https:\/\/css-tricks.com\/?p=318115"},"modified":"2020-08-10T07:32:48","modified_gmt":"2020-08-10T14:32:48","slug":"dont-wait-mock-the-api","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/dont-wait-mock-the-api\/","title":{"rendered":"Don’t Wait! Mock the API"},"content":{"rendered":"\n

Today we have a loose coupling between the front end and the back end of web applications. They are usually developed by separate teams, and keeping those teams and the technology in sync is not easy. To solve part of this problem, we can “fake” the API server that the back end tech would normally create and develop as if the API or endpoints already exist.<\/p>\n\n\n\n\n\n\n\n

The most common term used for creating simulated or “faking” a component is mocking<\/em>. Mocking allows you to simulate the API without (ideally) changing the front end. There are many ways to achieve mocking, and this is what makes it so scary for most people, at least in my opinion. <\/p>\n\n\n\n

Let\u2019s cover what a good API mocking should look like and how to implement a mocked API into a new or existing application.<\/p>\n\n\n\n

Note, the implementation that I am about to show is framework agnostic \u2014 so it can be used with any framework or vanilla JavaScript application.<\/p>\n\n\n

Mirage: The mocking framework<\/h3>\n\n\n

The mocking approach we are going to use is called Mirage<\/a>, which is somewhat new. I have tested many mocking frameworks and just recently discovered this one, and it\u2019s been a game changer for me.<\/p>\n\n\n\n

Mirage is marketed as a front-end-friendly framework that comes with a modern interface. It works in your browser, client-side, by intercepting XMLHttpRequest<\/code> and Fetch requests.<\/p>\n\n\n\n

We will go through creating a simple application with mocked API and cover some common problems along the way.<\/p>\n\n\n

Mirage setup<\/h3>\n\n\n

Let\u2019s create one of those standard to-do applications to demonstrate mocking. I will be using Vue as my framework of choice but of course, you can use something else since we\u2019re working with a framework-agnostic approach.<\/p>\n\n\n\n

So, go ahead and install Mirage in your project:<\/p>\n\n\n\n

# Using npm\nnpm i miragejs -D\n\u2028\n# Using Yarn\nyarn add miragejs -D<\/code><\/pre>\n\n\n\n

To start using Mirage, we need to setup a “server” (in quotes, because it\u2019s a fake server). Before we jump into the setup, I will cover the folder structure I found works best.<\/p>\n\n\n\n

\/\n\u251c\u2500\u2500 public\n\u251c\u2500\u2500 src\n\u2502   \u251c\u2500\u2500 api\n\u2502   \u2502   \u2514\u2500\u2500 mock\n\u2502   \u2502       \u251c\u2500\u2500 fixtures\n\u2502   \u2502       \u2502   \u2514\u2500\u2500 get-tasks.js\n\u2502   \u2502       \u2514\u2500\u2500 index.js\n\u2502   \u2514\u2500\u2500 main.js\n\u251c\u2500\u2500 package.json\n\u2514\u2500\u2500 package-lock.json<\/code><\/pre>\n\n\n\n

In a mock<\/code> directory, open up a new index.js<\/code> file and define your mock server:<\/p>\n\n\n\n

\/\/ api\/mock\/index.js\nimport { Server } from 'miragejs';\n\u2028\nexport default function ({ environment = 'development' } = {}) {\n\u00a0 return new Server({\n\u00a0 \u00a0 environment,\n\u2028\n\u00a0 \u00a0 routes() {\n\u00a0 \u00a0 \u00a0 \/\/ We will add our routes here\n\u00a0 \u00a0 },\n\u00a0 });\n}<\/code><\/pre>\n\n\n\n

The environment argument we’re adding to the function signature is just a convention. We can pass in a different environment as needed.<\/p>\n\n\n\n

Now, open your app bootstrap file. In our case, this is he src\/main.js<\/code> file since we are working with Vue. Import your createServer<\/code> function, and call it in the development environment<\/em>.<\/p>\n\n\n\n

\/\/ main.js\nimport createServer from '.\/mock'\n\u2028\nif (process.env.NODE_ENV === 'development') {\n\u00a0 \u00a0 createServer();\n}<\/code><\/pre>\n\n\n\n

We’re using the process.env.NODE_ENV<\/code> environment variable here, which is a common global variable. The conditional allows Mirage to be tree-shaken in production, therefore, it won’t affect your production bundle.<\/p>\n\n\n\n

That is all we need to set up Mirage! It\u2019s this sort of ease that makes the DX<\/a> of Mirage so nice.<\/p>\n\n\n\n

Our createServer<\/code> function is defaulting it to development<\/code> environment for the sake of making this article simple. In most cases, this will default to test<\/code> since, in most apps, you’ll call createServer<\/code> once in development mode but many times in test files.<\/p>\n\n\n

How it works<\/h3>\n\n\n

Before we make our first request, let’s quickly cover how Mirage works.<\/p>\n\n\n\n

Mirage is a client-side<\/strong> mocking framework, meaning all the mocking will happen in the browser, which Mirage does using the Pretender <\/a>library. Pretender will temporarily replace native XMLHttpRequest<\/code> and Fetch configurations, intercept all requests, and direct them to a little pretend service that the Mirage hooks onto.<\/p>\n\n\n\n

If you crack open DevTools and head into the Network tab, you won’t see any Mirage requests. That\u2019s because the request is intercepted and handled by Mirage (via Pretender in the back end). Mirage logs all requests, which we\u2019ll get to in just a bit.<\/p>\n\n\n

Let’s make requests!<\/h3>\n\n\n

Let’s create a request to an \/api\/tasks<\/code> endpoint that will return a list of tasks that we are going to show in our to-do app. Note that I\u2019m using axios<\/a> to fetch the data. That\u2019s just my personal preference. Again, Mirage works with native XMLHttpRequest<\/code>, Fetch, and any other library.<\/p>\n\n\n\n

\/\/ components\/tasks.vue\nexport default {\n\u00a0 async created() {\n\u00a0 \u00a0 try {\n\u00a0 \u00a0 \u00a0 const { data } = await axios.get('\/api\/tasks'); \/\/ Fetch the data\n\u00a0 \u00a0 \u00a0 this.tasks = data.tasks;\n\u00a0 \u00a0 } catch(e) {\n\u00a0 \u00a0 \u00a0 console.error(e);\n\u00a0 \u00a0 }\n\u00a0 }\n};<\/code><\/pre>\n\n\n\n

Opening your JavaScript console \u2014 there should be an error from Mirage in there:<\/p>\n\n\n\n

Mirage: Your app tried to GET '\/api\/tasks', but there was no route defined to handle this request.<\/code><\/pre>\n\n\n\n

This means Mirage is running, but the router hasn’t been mocked out yet. Let’s solve this by adding that route.<\/p>\n\n\n

Mocking requests<\/h3>\n\n\n

Inside our mock\/index.js<\/code> file, there is a routes()<\/code> hook. Route handlers allow us to define which URLs should be handled by the Mirage server.<\/p>\n\n\n\n

To define a router handler, we need to add it inside the routes()<\/code> function.<\/p>\n\n\n\n

\/\/ mock\/index.js\nexport default function ({ environment = 'development' } = {}) {\n\u00a0 \u00a0 \/\/ ...\n\u00a0 \u00a0 routes() {\n\u00a0 \u00a0 \u00a0 this.get('\/api\/tasks', () => ({\n\u00a0 \u00a0 \u00a0 \u00a0 tasks: [\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 { id: 1, text: \"Feed the cat\" },\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 { id: 2, text: \"Wash the dishes\" },\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \/\/...\n\u00a0 \u00a0 \u00a0 \u00a0 ],\n\u00a0 \u00a0 \u00a0 }))\n\u00a0 \u00a0 },\n\u00a0 });\n}<\/code><\/pre>\n\n\n\n

The routes()<\/code> hook is the way we define our route handlers. Using a this.get()<\/code> method lets us mock GET<\/code> requests. The first argument of all request functions is the URL we are handling, and the second argument is a function that responds with some data.<\/p>\n\n\n\n

As a note, Mirage accepts any HTTP request type, and each type has the same signature:<\/p>\n\n\n\n

this.get('\/tasks', (schema, request) => { ... });\nthis.post('\/tasks', (schema, request) => { ... });\nthis.patch('\/tasks\/:id', (schema, request) => { ... });\nthis.put('\/tasks\/:id', (schema, request) => { ... });\nthis.del('\/tasks\/:id', (schema, request) => { ... });\nthis.options('\/tasks', (schema, request) => { ... });<\/code><\/pre>\n\n\n\n

We will discuss the schema<\/code> and request<\/code> parameters of the callback function in a moment.<\/p>\n\n\n\n

With this, we have successfully mocked our route and we should see inside our console a successful response from Mirage.<\/p>\n\n\n\n

\"Screenshot<\/figure>\n\n\n

Working with dynamic data<\/h3>\n\n\n

Trying to add a new to-do in our app won’t be possible because our data in the GET<\/code> response has hardcoded values. Mirage’s solution to this is that they provide a lightweight data layer that acts as a database. Let’s fix what we have so far.<\/p>\n\n\n\n

Like the routes()<\/code> hook, Mirage defines a seeds()<\/code> hook. It allows us to create initial data for the server. I\u2019m going to move the GET<\/code> data to the seeds()<\/code> hook where I will push it to the Mirage database.<\/p>\n\n\n\n

seeds(server) {\n\u00a0 server.db.loadData({\n\u00a0 \u00a0 tasks: [\n\u00a0 \u00a0 \u00a0 { id: 1, text: \"Feed the cat\" },\n\u00a0 \u00a0 \u00a0 { id: 2, text: \"Wash the dishes\" },\n\u00a0 \u00a0 ],\n\u00a0 })\n},<\/code><\/pre>\n\n\n\n

I moved our static data from the GET<\/code> method to seeds()<\/code> hook, where that data is loaded into a faux database. Now, we need to refactor our GET<\/code> method to return data from that database. This is actually pretty straightforward \u2014 the first argument of the callback function of any route()<\/code> method is the schema.<\/p>\n\n\n\n

this.get('\/api\/tasks', (schema) => {\n\u00a0 return schema.db.tasks;\n})<\/code><\/pre>\n\n\n\n

Now we can add new to-do items to our app by making a POST<\/code> request:<\/p>\n\n\n\n

async addTask() {\n\u00a0 const { data } = await axios.post('\/api\/tasks', { data: this.newTask });\n\u00a0 this.tasks.push(data);\n\u00a0 this.newTask = {};\n},<\/code><\/pre>\n\n\n\n

We mock this route in Mirage by creating a POST \/api\/tasks<\/code> route handler:<\/p>\n\n\n\n

this.post('\/tasks', (schema, request) => {})<\/code><\/pre>\n\n\n\n

Using the second parameter of the callback function, we can see the sent request.<\/p>\n\n\n\n

\"Screenshot<\/figure>\n\n\n\n

Inside the requestBody<\/code> property is the data that we sent. That means it\u2019s now available for us to create a new task.<\/p>\n\n\n\n

this.post('\/api\/tasks', (schema, request) => {\n\u00a0 \/\/ Take the send data from axios.\n\u00a0 const task = JSON.parse(request.requestBody).data\n\u2028\n\u00a0 return schema.db.tasks.insert(task)\n})<\/code><\/pre>\n\n\n\n

The id<\/code> of the task will be set by the Mirage\u2019s database by default. Thus, there is no need to keep track of ids and send them with your request \u2014 just like a real server.<\/p>\n\n\n

Dynamic routes? Sure!<\/h3>\n\n\n

The last thing to cover is dynamic routes. They allow us to use a dynamic segment in our URL, which is useful for deleting or updating a single to-do item in our app.<\/p>\n\n\n\n

Our delete request should go to \/api\/tasks\/1<\/code>, \/api\/tasks\/2<\/code>, and so on. Mirage provides a way for us  to define a dynamic segment in the URL, like this:<\/p>\n\n\n\n

this.delete('\/api\/tasks\/:id', (schema, request) => {\n\u00a0 \/\/ Return the ID from URL.\n\u00a0 const id = request.params.id;\n\u2028\n\u00a0 return schema.db.tasks.remove(id);\n})<\/code><\/pre>\n\n\n\n

Using a colon (:<\/code>) in the URL is how we define a dynamic segment in our URL. After the colon, we specify the name of the segment which, in our case, is called id<\/code> and maps to the ID of a specific to-do item. We can access the value of the segment via the request.params<\/code> object, where the property name corresponds to the segment name \u2014 request.params.id<\/code>. Then we use the schema to remove an item with that same ID from the Mirage database.<\/p>\n\n\n\n

If you\u2019ve noticed, all of my routes so far are prefixed with api\/<\/code>. Writing this over and over can be cumbersome and you may want to make it easier. Mirage offers the namespace<\/code> property that can help. Inside the routes hook, we can define the namespace<\/code> property so we don’t have to write that out each time.<\/p>\n\n\n\n

routes() {\n\u00a0\/\/ Prefix for all routes.\n\u00a0this.namespace = '\/api';\n\u2028\n\u00a0this.get('\/tasks', () => { ... })\n\u00a0this.delete('\/tasks\/:id', () => { ... })\n\u00a0this.post('\/tasks', () => { ... })\n}<\/code><\/pre>\n\n\n

OK, let\u2019s integrate this into an existing<\/em> app<\/h3>\n\n\n

So far, everything we\u2019ve looked at integrates Mirage into a new app. But what about adding Mirage to an existing application? Mirage has you covered so you don’t have to mock your entire API.<\/p>\n\n\n\n

The first thing to note is that adding Mirage to an existing application will throw an error if the site makes a request that isn’t handled by Mirage. To avoid this, we can tell Mirage to pass through all unhandled requests.<\/p>\n\n\n\n

routes() {\n\u00a0 this.get('\/tasks', () => { ... })\n\u00a0\u00a0\n\u00a0 \/\/ Pass through all unhandled requests.\n\u00a0 this.passthrough()\n}<\/code><\/pre>\n\n\n\n

Now we can develop on top of an existing API with Mirage handling only the missing parts of our API.<\/p>\n\n\n\n

Mirage can even change the base URL of which it captures the requests. This is useful because, usually, a server won’t live on localhost:3000<\/code> but rather on a custom domain.<\/p>\n\n\n\n

routes() {\n\u00a0\/\/ Set the base route.\n\u00a0this.urlPrefix = 'https:\/\/devenv.ourapp.example';\n\u2028\n\u00a0this.get('\/tasks', () => { ... })\n}<\/code><\/pre>\n\n\n\n

Now, all of our requests will point to the real API server, but Mirage will intercept them like it did when we set it up with a new app. This means that the transition from Mirage to the real API is pretty darn seamless \u2014 delete the route from the mock server and things are good to go.<\/p>\n\n\n

Wrapping up<\/h3>\n\n\n

Over the course of five years, I have used many mocking frameworks, yet I never truly liked any of the solutions out there. That was until recently, when my team was faced with a need for a mocking solution and I found out about Mirage.<\/p>\n\n\n\n

Other solutions, like the commonly used JSON-Server<\/a>, are external processes that need to run alongside the front end. Furthermore, they are often nothing more than an Express server with utility functions on top. The result is that front-end developers like us need to know about middleware, NodeJS, and how servers work\u2026 things many of us probably don\u2019t want to handle. Other attempts, like Mockoon<\/a>, have a complex interface while lacking much-needed features. There\u2019s another group of frameworks that are only used for testing, like the popular SinonJS<\/a>. Unfortunately, these frameworks can’t be used to mock the regular behavior.<\/p>\n\n\n\n

My team managed to create a functioning server that enables us to write front-end code as if we were working with a real back-end. We did it by writing the front-end codebase without any external processes or servers that are needed to run. This is why I love Mirage. It is really simple to set up, yet powerful enough to handle anything that\u2019s thrown at it. You can use it for basic applications that return a static array to full-blown back-end apps alike \u2014 regardless of whether it\u2019s a new or existing app.<\/p>\n\n\n\n

There\u2019s a lot more to Mirage beyond the implementations we covered here. A working example of what we covered can be found on GitHub<\/a>. (Fun fact: Mirage also works with GraphQL!) Mirage has well-written documentation<\/a> that includes a bunch of step-by-step tutorials, so be sure to check it out.<\/p>\n","protected":false},"excerpt":{"rendered":"

Today we have a loose coupling between the front end and the back end of web applications. They are usually developed by separate teams, and keeping those teams and the technology in sync is not easy. To solve part of this problem, we can “fake” the API server that the back end tech would normally […]<\/p>\n","protected":false},"author":274691,"featured_media":318121,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_bbp_topic_count":0,"_bbp_reply_count":0,"_bbp_total_topic_count":0,"_bbp_total_reply_count":0,"_bbp_voice_count":0,"_bbp_anonymous_reply_count":0,"_bbp_topic_count_hidden":0,"_bbp_reply_count_hidden":0,"_bbp_forum_subforum_count":0,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"c2c_always_allow_admin_comments":false,"footnotes":"","jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":[]},"categories":[4],"tags":[667,17312],"jetpack_publicize_connections":[],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2020\/07\/mirage-js.png?fit=1200%2C600&ssl=1","jetpack-related-posts":[{"id":268728,"url":"https:\/\/css-tricks.com\/figma-web-api\/","url_meta":{"origin":318115,"position":0},"title":"Figma Web API","date":"March 23, 2018","format":false,"excerpt":"Figma launched their Web Platform API which allows developers to build on top of and extend the features of their web-based design tool. They also published a quick post about the release that showcases how design teams at Uber and GitHub are starting to use the API but they also\u2026","rel":"","context":"In "Link"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2018\/03\/figma.jpg?fit=1200%2C600&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":263659,"url":"https:\/\/css-tricks.com\/breaking-performance-api\/","url_meta":{"origin":318115,"position":1},"title":"Breaking Down the Performance API","date":"December 20, 2017","format":false,"excerpt":"JavaScript\u2019s Performance API is prudent, because it hands over tools to accurately measure the performance of Web pages, which, in spite of being performed since long before, never really became easy or precise enough. That said, it isn\u2019t as easy to get started with the API as it is to\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2017\/12\/performance_api_illustration.jpg?fit=1038%2C491&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":273398,"url":"https:\/\/css-tricks.com\/reinvest-your-time-with-hellosign-api\/","url_meta":{"origin":318115,"position":2},"title":"\u200bReinvest Your Time with HelloSign API","date":"July 5, 2018","format":false,"excerpt":"HelloSign API makes it simple to embed secure and legally binding eSignatures directly into any website. It's 2x faster to implement than other eSign solutions and is also the only eSign API that allows customers to completely white label the integration, meaning our customers can give their customers a seamless,\u2026","rel":"","context":"In "Link"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2018\/06\/reinvest.png?fit=1200%2C787&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":277871,"url":"https:\/\/css-tricks.com\/the-most-flexible-esign-api\/","url_meta":{"origin":318115,"position":3},"title":"The Most Flexible eSign API","date":"October 25, 2018","format":false,"excerpt":"With our robust SDK, super clean dashboard, detailed documentation, and world-class support, HelloSign API is one of the most flexible and powerful API on the market. Start building for free today.","rel":"","context":"In "Link"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2018\/10\/API-Pocket-Ad-1220x560-1.png?fit=1200%2C551&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":7055,"url":"https:\/\/css-tricks.com\/wufoo-api-contest\/","url_meta":{"origin":318115,"position":4},"title":"Wufoo API Contest","date":"August 11, 2010","format":false,"excerpt":"We're giving away a friggin' Battle Axe (and $3,000) to the winner of the Wufoo API contest. Cash prizes and Wufoo accounts for life to the second and third place winners as well. Developers, you have until the end of this month! Check out the page for ideas and API\u2026","rel":"","context":"In "Link"","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":268064,"url":"https:\/\/css-tricks.com\/hellosign-api\/","url_meta":{"origin":318115,"position":5},"title":"\u200bHelloSign API: Everything IT requires and Developers love.","date":"March 15, 2018","format":false,"excerpt":"We know that no API can write your code for you (unfortunately), but ours comes close. With in-depth documentation, customizable features, amazing support, and a dashboard that makes your code easy to debug, you won't find an eSignature product with an easier path to implementation. Or that's more liked by\u2026","rel":"","context":"In "Link"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2018\/03\/HellosignAPI.png?fit=1200%2C714&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]}],"featured_media_src_url":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2020\/07\/mirage-js.png?fit=1024%2C512&ssl=1","_links":{"self":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/318115"}],"collection":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/users\/274691"}],"replies":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/comments?post=318115"}],"version-history":[{"count":11,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/318115\/revisions"}],"predecessor-version":[{"id":335587,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/318115\/revisions\/335587"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/media\/318121"}],"wp:attachment":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/media?parent=318115"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/categories?post=318115"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/tags?post=318115"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}