Hi everyone! I hit a wall while I was building the smart home dashboard, but I’m here now. Consistency is hard sometimes, but I guess what matters is showing up when it is needed.
The smart home dashboard system is complex and involves various devices that communicate through APIs. Step 4 in my roadmap to build the dashboard involves creating mock data to create certain functionalities for the app pending the time I find an actual API to use for it.
This is where Mirage.js comes in, offering a solution to mock backend services and simulate real-world data. Basically, it is helping me to build and test the frontend when the backend isn’t available. In this article, we will look at how to use Mirage.js to create a mock API for the smart home dashboard.
What Is Mirage.js and Why Do We Use It?
Mirage.js is a library that creates a mock server right in your browser. It can be thought of as a temporary stand-in for your real backend server. It's particularly useful when:
Your backend API isn't ready yet
You want to prototype features quickly
You need to test how your frontend handles different API responses
You're working offline
For my smart home dashboard, I needed to test different devices (lights, humidifiers, speakers, air conditioners etc) and how they would interact with the interface before connecting to real IoT devices' API. Here’s how I did it.
Setting Up Mirage.js
STEP 1: Install Mirage.js
First, you need to install mirage.js in your React TypeScript project.
npm install –save-dev miragejs
STEP 2: Create the basic server
Create a new file, and name it mirage.ts
in your src directory. This is what the basic structure looks like:
import { createServer } from "miragejs";
export function makeServer({ environment = "development" } = {}) {
return createServer({
environment,
models: {
// We'll define our data models here
},
seeds(server) {
// We'll add sample data here
},
routes() {
// We'll define API endpoints here
},
});
}
At this point, you need to have decided on the appliances that you want to mock and their properties such as id
, type
, room
, status
, etc.
For this project, some of the appliances to be used include light, humidifier, speaker, appliances (e.g TV, AC, heater).
STEP 3: Define typescript interfaces
Before we create our mock data, we have to define the types for our smart home devices:
interface Light {
id: number;
room: string;
status: "on" | "off";
brightness: number;
color: string;
schedule: string | null;
}
interface Humidifier {
id: number;
room: string;
status: "on" | "off";
schedule: string | null;
energyEfficiency: boolean;
}
interface Appliance {
id: number;
type: "TV" | "Heater" | "AC";
room: string;
status: "on" | "off";
}
interface Speaker = {
id: number;
room: string;
status: "playing" | "off";
volume: number;
energy: number;
};
interface AppSchema {
light: Light;
humidifier: Humidifier;
appliance: Appliance;
Speaker: Speaker
}
STEP 4: Set up models and seed data
Here, you get to create our mock server with sample data:
import { createServer, Model, Server } from "miragejs";
import Schema from "miragejs/orm/schema";
export function makeServer({ environment = "development" } = {}): Server {
return createServer({
environment,
models: {
light: Model.extend<Partial<Light>>({}),
humidifier: Model.extend<Partial<Humidifier>>({}),
appliances: Model.extend<Partial<Appliance>>({}),
speaker: Model.extend<Partial<Speaker>>({}),
}
seeds(server){
//we are creating sample appliances
server.create("appliance", {
id: 1,
type: "TV",
room: "Living Room",
status: "off"
});
server.create("appliance", {
id: 2,
type: "Heater",
room: "Bathroom",
status: "off",
});
server.create("appliance", {
id: 3,
type: "Air Conditioner",
room: "Bedroom",
status: "on",
});
//creating sample light
server.create("light", {
id: 1,
room: "Living Room",
status: "off",
brightness: 50,
color: "white",
schedule: null,
});
//creating sample humidifier
server.create("humidifier", {
id: 1,
room: "Bathroom",
status: "on",
schedule: "7:00 AM",
energyEfficiency: true,
});
},
routes(){
this.namespace = “api”;
//the GET endpoints
this.get("/lights", (schema: Schema<AppSchema>) => schema.all("light"));
this.get("/humidifiers", (schema: Schema<AppSchema>) => schema.all("humidifier"));
this.get("/appliances", (schema: Schema<AppSchema>) => schema.all("appliance"));
this.get("/speakers", (schema: Schema<AppSchema>) => schema.all("speaker"));
//POST endpoints for toggling devices.
//In our dashboard, users can toggle some of the devices on and off
this.post("/lights/:id/toggle", (schema: Schema<AppSchema>, request) => {
const id = request.params.id;
const light = schema.find("light", id);
if (light) {
const newStatus = light.status === "on" ? "off" : "on";
return light.update({ status: newStatus });
}
return null;
});
},
} );
}
STEP 5: Starting the mirage server
To ensure that the server runs when the application is launched, you need to initialize Mirage.js in the main entry file i.e App.tsx
import {makeServer} from “./components/api/Mirage”;
if(import.meta.env.MODE === “development”){
makeServer();
}
STEP 6: Using the Mock API in Components
With Mirage.js set up, we can now fetch data in the frontend just as you would with a real API.
Now, you can use your mock API in one of your components like this:
import { useEffect, useState } from 'react';
import { Light} from “./components/api/Mirage”;
interface Light {
id: number;
room: string;
status: "on" | "off";
brightness: number;
color: string;
schedule: string | null;
}
export const LightControls = () => {
const [lights, setLights] = useState<Light[]>([]);
useEffect(() => {
fetch('/api/lights')
.then(res => res.json())
.then(data => setLights(data.lights));
}, []);
const toggleLight = async (id: string) => {
const response = await fetch(`/api/lights/${id}/toggle`, {
method: 'POST',
});
const updatedLight = await response.json();
setLights(prevLights =>
prevLights.map(light =>
light.id === id ? updatedLight : light
)
);
};
return (
<div className="grid gap-4 grid-cols-2 md:grid-cols-3">
{lights.map(light => (
<div key={light.id} className="p-4 border rounded-lg">
<h3>{light.room} Light</h3>
<p>Status: {light.status}</p>
<button
onClick={() => toggleLight(light.id)}
className="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
>
Switch
</button>
</div>
))}
</div>
);
};
Extra Tips
Make sure your namespace in routes matches your API calls. It took me a while to figure that our because I forgot the
/api
prefix.Create seed data that resembles your real data as much as possible. It will make transitioning to the real API smoother.
Start with the simplest functionality, then add complex ones gradually.
Conclusion
Mirage.js allowed me to build and test the frontend independently of the backend, speeding up the development process. In this smart home dashboard project, I used it to mock appliance data, allowing me to build out the device control interface without relying on a live backend API. This not only speeds up development but also ensures a smoother transition when the real API becomes available.
The next step in this project is to implement user authentication in the app.
See you soon!
Top comments (0)