惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Attack and Defense Labs
Attack and Defense Labs
N
News and Events Feed by Topic
L
LINUX DO - 热门话题
PCI Perspectives
PCI Perspectives
www.infosecurity-magazine.com
www.infosecurity-magazine.com
爱范儿
爱范儿
D
DataBreaches.Net
Simon Willison's Weblog
Simon Willison's Weblog
S
Secure Thoughts
S
SegmentFault 最新的问题
博客园 - 【当耐特】
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 叶小钗
P
Proofpoint News Feed
The Hacker News
The Hacker News
T
ThreatConnect
N
News and Events Feed by Topic
T
Threatpost
The Register - Security
The Register - Security
WordPress大学
WordPress大学
博客园 - Franky
Recorded Future
Recorded Future
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Project Zero
Project Zero
大猫的无限游戏
大猫的无限游戏
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
罗磊的独立博客
Stack Overflow Blog
Stack Overflow Blog
腾讯CDC
F
Future of Privacy Forum
F
Full Disclosure
Cyberwarzone
Cyberwarzone
J
Java Code Geeks
李成银的技术随笔
Schneier on Security
Schneier on Security
Know Your Adversary
Know Your Adversary
H
Hacker News: Front Page
人人都是产品经理
人人都是产品经理
博客园_首页
Scott Helme
Scott Helme
Google DeepMind News
Google DeepMind News
美团技术团队
Malwarebytes
Malwarebytes
Last Week in AI
Last Week in AI
T
Tailwind CSS Blog
T
The Exploit Database - CXSecurity.com
G
GRAHAM CLULEY
Recent Announcements
Recent Announcements
C
CXSECURITY Database RSS Feed - CXSecurity.com

CSS-Tricks

Revealing Text With CSS letter-spacing | CSS-Tricks Technical Writing in the AI Age | CSS-Tricks Cross-Document View Transitions: Scaling Across Hundreds of Elements | CSS-Tricks Cross-Document View Transitions: Scaling Across Hundreds of Elements | CSS-Tricks The State of CSS Centering in 2026 | CSS-Tricks Stack Overflow: When We Stop Asking | CSS-Tricks Cross-Document View Transitions: The Gotchas Nobody Mentions | CSS-Tricks What’s !important #11: 3D Voxel Scenes, Flying Focus, CSS Syntaxes, and More | CSS-Tricks Computing and Displaying Discounted Prices in CSS | CSS-Tricks rotateX() | CSS-Tricks rotateY() | CSS-Tricks rotateZ() | CSS-Tricks rotate() | CSS-Tricks Soon We Can Finally Banish JavaScript to the ShadowRealm | CSS-Tricks Using CSS corner-shape For Folded Corners | CSS-Tricks A Scrollytelling Gift for Mum on Mother’s Day 2026 | CSS-Tricks Google’s Prompt API | CSS-Tricks Making Zigzag CSS Layouts With a Grid + Transform Trick | CSS-Tricks Fixed-Height Cards: More Fragile Than They Look | CSS-Tricks What’s !important #10: HTML-in-Canvas, Hex Maps, E-ink Optimization, and More | CSS-Tricks The Importance of Native Randomness in CSS | CSS-Tricks contrast() | CSS-Tricks contrast-color() | CSS-Tricks Let’s Use the Nonexistent ::nth-letter Selector Now | CSS-Tricks Quick Hit #126 Recreating Apple’s Vision Pro Animation in CSS | CSS-Tricks Quick Hit #125 Enhancing Astro With a Markdown Component | CSS-Tricks Quick Hit #124 Markdown + Astro = ❤️ | CSS-Tricks Quick Hit #123 What’s !important #9: clip-path Jigsaws, View Transitions Toolkit, Name-only Containers, and More | CSS-Tricks A Well-Designed JavaScript Module System is Your First Architecture Decision | CSS-Tricks hypot() | CSS-Tricks The Radio State Machine | CSS-Tricks 7 View Transitions Recipes to Try | CSS-Tricks Quick Hit #122 Quick Hit #121 Selecting a Date Range in CSS | CSS-Tricks saturate() | CSS-Tricks justify-self | CSS-Tricks Quick Hit #120 Alternatives to the !important Keyword | CSS-Tricks Quick Hit #119 New CSS Multi-Column Layout Features in Chrome | CSS-Tricks Quick Hit #118 Making Complex CSS Shapes Using shape() | CSS-Tricks Quick Hit #117 Front-End Fools: Top 10 April Fools’ UI Pranks of All Time | CSS-Tricks Sniffing Out the CSS Olfactive API | CSS-Tricks What’s !important #8: Light/Dark Favicons, @mixin, object-view-box, and More | CSS-Tricks Quick Hit #116 Form Automation Tips for Happier User and Clients | CSS-Tricks Quick Hit #115 Generative UI Notes | CSS-Tricks Quick Hit #114 Quick Hit #113 Experimenting With Scroll-Driven corner-shape Animations | CSS-Tricks Quick Hit #112 JavaScript for Everyone: Destructuring | CSS-Tricks Quick Hit #111 Quick Hit #110 What’s !important #7: random(), Folded Corners, Anchored Container Queries, and More | CSS-Tricks 4 Reasons That Make Tailwind Great for Building Layouts | CSS-Tricks Quick Hit #109 Quick Hit #108 Abusing Customizable Selects | CSS-Tricks Quick Hit #107 The Value of z-index | CSS-Tricks Quick Hit #106 The Different Ways to Select <html> in CSS Quick Hit #105 Popover API or Dialog API: Which to Choose? Quick Hit #104 What’s !important #6: :heading, border-shape, Truncating Text From the Middle, and More Yet Another Way to Center an (Absolute) Element An Exploit ... in CSS?! Quick Hit #103 A Complete Guide to Bookmarklets Quick Hit #102 Loading Smarter: SVG vs. Raster Loaders in Modern Web Design Potentially Coming to a Browser :near() You Quick Hit #101 Distinguishing "Components" and "Utilities" in Tailwind Quick Hit #100 Spiral Scrollytelling in CSS With sibling-index() Interop 2026 Quick Hit #99 What’s !important #5: Lazy-loading iframes, Repeating corner-shape Backgrounds, and More Quick Hit #98 Making a Responsive Pyramidal Grid With Modern CSS Approximating contrast-color() With Other CSS Features Quick Hit #97 Trying to Make the Perfect Pie Chart in CSS Quick Hit #96 Quick Hit #95 CSS Bar Charts Using Modern Functions Quick Hit #94 No Hassle Visual Code Theming: Publishing an Extension Quick Hit #93
Don't Wait! Mock the API
CSS-Tricks · 2020-08-10 · via CSS-Tricks

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.

The most common term used for creating simulated or “faking” a component is mocking. 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. 

Let’s cover what a good API mocking should look like and how to implement a mocked API into a new or existing application.

Note, the implementation that I am about to show is framework agnostic — so it can be used with any framework or vanilla JavaScript application.

Mirage: The mocking framework

The mocking approach we are going to use is called Mirage, which is somewhat new. I have tested many mocking frameworks and just recently discovered this one, and it’s been a game changer for me.

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 and Fetch requests.

We will go through creating a simple application with mocked API and cover some common problems along the way.

Mirage setup

Let’s 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’re working with a framework-agnostic approach.

So, go ahead and install Mirage in your project:

# Using npm
npm i miragejs -D


# Using Yarn
yarn add miragejs -D

To start using Mirage, we need to setup a “server” (in quotes, because it’s a fake server). Before we jump into the setup, I will cover the folder structure I found works best.

/
├── public
├── src
│   ├── api
│   │   └── mock
│   │       ├── fixtures
│   │       │   └── get-tasks.js
│   │       └── index.js
│   └── main.js
├── package.json
└── package-lock.json

In a mock directory, open up a new index.js file and define your mock server:

// api/mock/index.js
import { Server } from 'miragejs';


export default function ({ environment = 'development' } = {}) {
  return new Server({
    environment,


    routes() {
      // We will add our routes here
    },
  });
}

The environment argument we’re adding to the function signature is just a convention. We can pass in a different environment as needed.

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

// main.js
import createServer from './mock'


if (process.env.NODE_ENV === 'development') {
    createServer();
}

We’re using the process.env.NODE_ENV 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.

That is all we need to set up Mirage! It’s this sort of ease that makes the DX of Mirage so nice.

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

How it works

Before we make our first request, let’s quickly cover how Mirage works.

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

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

Let’s make requests!

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

// components/tasks.vue
export default {
  async created() {
    try {
      const { data } = await axios.get('/api/tasks'); // Fetch the data
      this.tasks = data.tasks;
    } catch(e) {
      console.error(e);
    }
  }
};

Opening your JavaScript console — there should be an error from Mirage in there:

Mirage: Your app tried to GET '/api/tasks', but there was no route defined to handle this request.

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

Mocking requests

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

To define a router handler, we need to add it inside the routes() function.

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

The routes() hook is the way we define our route handlers. Using a this.get() method lets us mock GET 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.

As a note, Mirage accepts any HTTP request type, and each type has the same signature:

this.get('/tasks', (schema, request) => { ... });
this.post('/tasks', (schema, request) => { ... });
this.patch('/tasks/:id', (schema, request) => { ... });
this.put('/tasks/:id', (schema, request) => { ... });
this.del('/tasks/:id', (schema, request) => { ... });
this.options('/tasks', (schema, request) => { ... });

We will discuss the schema and request parameters of the callback function in a moment.

With this, we have successfully mocked our route and we should see inside our console a successful response from Mirage.

Screenshot of a Mirage response in the console showing data for two task objects with IDs 1 and 2.

Working with dynamic data

Trying to add a new to-do in our app won’t be possible because our data in the GET 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.

Like the routes() hook, Mirage defines a seeds() hook. It allows us to create initial data for the server. I’m going to move the GET data to the seeds() hook where I will push it to the Mirage database.

seeds(server) {
  server.db.loadData({
    tasks: [
      { id: 1, text: "Feed the cat" },
      { id: 2, text: "Wash the dishes" },
    ],
  })
},

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

this.get('/api/tasks', (schema) => {
  return schema.db.tasks;
})

Now we can add new to-do items to our app by making a POST request:

async addTask() {
  const { data } = await axios.post('/api/tasks', { data: this.newTask });
  this.tasks.push(data);
  this.newTask = {};
},

We mock this route in Mirage by creating a POST /api/tasks route handler:

this.post('/tasks', (schema, request) => {})

Using the second parameter of the callback function, we can see the sent request.

Screenshot of the mocking server request. The requestBody property is highlighted in yellow and contains text data that says Hello CSS-Tricks.

Inside the requestBody property is the data that we sent. That means it’s now available for us to create a new task.

this.post('/api/tasks', (schema, request) => {
  // Take the send data from axios.
  const task = JSON.parse(request.requestBody).data


  return schema.db.tasks.insert(task)
})

The id of the task will be set by the Mirage’s database by default. Thus, there is no need to keep track of ids and send them with your request — just like a real server.

Dynamic routes? Sure!

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.

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

this.delete('/api/tasks/:id', (schema, request) => {
  // Return the ID from URL.
  const id = request.params.id;


  return schema.db.tasks.remove(id);
})

Using a colon (:) 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 and maps to the ID of a specific to-do item. We can access the value of the segment via the request.params object, where the property name corresponds to the segment name — request.params.id. Then we use the schema to remove an item with that same ID from the Mirage database.

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

routes() {
 // Prefix for all routes.
 this.namespace = '/api';


 this.get('/tasks', () => { ... })
 this.delete('/tasks/:id', () => { ... })
 this.post('/tasks', () => { ... })
}

OK, let’s integrate this into an existing app

So far, everything we’ve 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.

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.

routes() {
  this.get('/tasks', () => { ... })
  
  // Pass through all unhandled requests.
  this.passthrough()
}

Now we can develop on top of an existing API with Mirage handling only the missing parts of our API.

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 but rather on a custom domain.

routes() {
 // Set the base route.
 this.urlPrefix = 'https://devenv.ourapp.example';


 this.get('/tasks', () => { ... })
}

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 — delete the route from the mock server and things are good to go.

Wrapping up

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.

Other solutions, like the commonly used JSON-Server, 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… things many of us probably don’t want to handle. Other attempts, like Mockoon, have a complex interface while lacking much-needed features. There’s another group of frameworks that are only used for testing, like the popular SinonJS. Unfortunately, these frameworks can’t be used to mock the regular behavior.

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’s thrown at it. You can use it for basic applications that return a static array to full-blown back-end apps alike — regardless of whether it’s a new or existing app.

There’s a lot more to Mirage beyond the implementations we covered here. A working example of what we covered can be found on GitHub. (Fun fact: Mirage also works with GraphQL!) Mirage has well-written documentation that includes a bunch of step-by-step tutorials, so be sure to check it out.