Or, what to do when the backend API is not done when it is supposed to.
As a contractor I have heard this so many times: Relax, the backend server will be ready long before you will start to develop the front end applikation. As a contractor with tight estimates, this can be very annoying, as you know that this will slow you down, and make it difficult to meet the estimated schedule and deadlines.
So what to do? You need to mock up something to make do. In many tutorials, they place JSON files in a server structure, wich will work using GET requests. Next step is to create a temporary server in C# or node, serving mock data, to get started.
But when browsing through a lot of conference videos, I stopped at one that had a great solution. Build a mocking API backend that feels real with MSW.js by Thor(Shenghan) Chen. MSW, or Mock Service Worker, is a small library, that creates a service worker. One of the techniques in creating PWA’s (Pprogressive Web Applications) is to set up service workers and intercept rest calls from Axios or Fetch, then you may use the local database or cache api to make the application more responsive, or even work without internet connection.
The applictation
To demonstrate the features we will create a small react CRUD application, with a list view, and a details page.
The details will look like this:
MSW to the rescue!
The MSW makes it possible to mock a backend api of both REST and GraphQL, and you can run the mocs in the browser or as a node server. No need to create a mock server to get started. All the code you need will be created inside the react application, and it is easy to set it up so it only mocks when using the IDE. This is also a great feature, because now you dont have to change anything when deploying to test or production.
So we will now create a small React application, and install msw using the following steps:
- Create a new React application
- Add the MSW library
- Install the MSW service worker in the public directory
- Add the react router to the project
- Create a Mock dir
- Let the application only start MSW service worker, when in Development mode.
npx create-react-app explore-msw cd explore-msw npx install msw --save-dev npx msw init ./public npx install react-router-dom npm install
Now we have small project. Edit the index.js file and the lines for starting the msw serviceworker before the ReactDom.Render. Check the existence of mockServiceWorker.js is located in the public directory.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
// Start the msw serviceworker when in dev mode
if (process.env.NODE_ENV === 'development') {
const { worker } = require('./mocks/browser')
worker.start()
}
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
Create the Mocking files
To mock data, we need to create a folder \src\mock. In this folder we create two files
browser.js
This file instantiates the serviceworker, it loads handlers for handling all the mocked requests
import { setupWorker } from 'msw'
import { handlers } from './handlers'
export const worker = setupWorker(...handlers)
handlers.js
In this file, you import handlers from all the files mocking requests:
import { customerDetailsMockHandler } from "../pages/customerdetails/customerDetailsMockHandler";
import { customersListMockHandlers } from "../pages/customerslist/customersListMockHandlers";
export const handlers = [
...customersListMockHandlers,
...customerDetailsMockHandler
]
The mocks
In this small example, we create a small crud application, with two forms, on listing customers, and a detail form for CRUD. We create two more folders for these components and their respective moc files:
/src/customerdetails
/src/customerslist
/src/customerslist/customersListMockHandlers.js
This file mocs a GET request, and simply returns a const array.
import { rest } from 'msw'
const CUSTOMERS =
[
{ id: 1, name: 'ACME inc', ceo: 'Wiley Coyote' },
{ id: 2, name: 'Amigone Funeral Home', ceo: 'Mr. Under Taker' },
{ id: 3, name: 'Little Hope Cemetery', ceo: 'Rev. Hope' },
{ id: 4, name: 'Hindenburger', ceo: 'Chef Alfred' },
{ id: 5, name: 'Thai Tanic', ceo: 'Chef Mai Tai' }
]
export const customersListMockHandlers = [
rest.get('/customerlist', (req, res, ctx) => {
return res(
ctx.json(CUSTOMERS)
)
})
]
/src/customerdetails/customerDetailsMockHandler.js
This mock, is more complicated, and mocs GET, POST and DELETE.
Note use of parameters in the GET and DELETE requests, that picks up values passed with the request. This example will return OK if id is ‘1’, and otherwise error.
The POST request uses JSON.parse on req.body to get the passed parameters.
import { rest } from 'msw'
export const customerDetailsMockHandler = [
// Handling GET requests
rest.get('/customer/:id', (req, res, ctx) => {
const { id } = req.params
if (id === '1') {
return res(
ctx.json({ id: 1, name: 'ACME inc', ceo: 'Wiley Coyote' })
)
} else {
return res(
ctx.status(404),
ctx.json({
errorMessage: `Customer not found`,
}),
)
}
}),
rest.post('/customer', (req, res, ctx) => {
const customer = JSON.parse(req.body)
if ((!customer.name) )
return res(
ctx.status(403),
ctx.json({
errorMessage: `Name is empty`,
}),
)
return res(
ctx.json({
...customer, customerId: !customer.customerId ? '21' : customer.customerId
})
)
}),
rest.delete('/customer/:id', (req, res, ctx) => {
const { id } = req.params
if (id === '1') {
return res(
ctx.json({
message: `Customer deleted`,
})
)
} else {
return res(
ctx.status(404),
ctx.json({
errorMessage: `Customer not found`,
})
)
}
}),
]
With this done, the application will now return mocked values on request. To use this we create two pages/components. To use these pages we first change the App.js.
App.js
Lets make use of the router, and create a small menu (currently only link to one item, home).
Below we setup a switch to the two components in the solution. We use Route to connect the URL to a component. Note that the CustomerDetails needs a parameter.
import {BrowserRouter as Router, Switch, Route, Link} from "react-router-dom";
import './App.css';
import {CustomerDetails} from "./pages/customerdetails/CustomerDetailsComponent";
import {CustomersList} from './pages/customerslist/CustomersListComponent';
function App() {
return (
<Router>
<div>
<div>
<span><Link to="/">Home</Link></span>
</div>
<div>
<Switch>
<Route path="/customerdetails/:customerId" render={(props) => <CustomerDetails {...props.match.params} />} />
<Route path="/">
<CustomersList />
</Route>
</Switch>
</div>
</div>
</Router>
);
}
export default App;
/src/customerslist/CustomersListComponent.js
In this file,
import { useEffect, useState } from "react"
import { NavLink } from "react-router-dom"
export const CustomersList = (props) => {
const [isloading, setIsloading] = useState(false)
const [errorMessage, setErrorMessage] = useState('')
const [customers, setCustomers] = useState([])
async function fetchCustomers() {
setIsloading(true)
setErrorMessage('')
const fetchResult = await fetch(`/customerlist`, { method: 'GET' })
const result = await fetchResult.json()
if (fetchResult.ok) {
setCustomers([...result])
setIsloading(false)
return;
} else {
setIsloading(false)
setErrorMessage(result.errorMessage)
}
}
useEffect(() => {
fetchCustomers();
}, [])
if (isloading)
return (<div>loading data</div>)
else if (errorMessage)
return (<div>something bad has happened: {errorMessage}</div>)
else return (
<div>
<h1>Customer list</h1>
{customers.map(function (item, i) {
return (
<div key={i}>
<NavLink to={`/customerdetails/${item.id}`}>
<b>{item.name}</b>
</NavLink>
<span>ceo: {item.ceo}</span>
</div>
)
})
}
</div>
)
}
/src/customerdetails/CustomerDetailsComponent.js
The detailscomponent has input fields for editing data, and a row of buttons on the bottom. To keep this file short, I have created a custom hook called useCustomerDetails. This hook encapsulates all logic.
import { useCustomerDetails } from "./useCustomerDetails"
export const CustomerDetails = (props) => {
const {
isloading, errorMessage, message, customer, fetchCustomer,
clear, postCustomer, deleteCustomer, formValueOnChange } = useCustomerDetails(props)
const spanStyle = {
width: "100px",
display: "inline-block"
}
if (isloading)
return (<div>loading data</div>)
else if (errorMessage)
return (<div>something bad has happened: {errorMessage}</div>)
else
return (
<div>
<div>
<span style={spanStyle} >Id</span>
<input
data-testid="id" id="id" name="id"
value={customer.id}
onChange={formValueOnChange}
/>
</div>
<div>
<span style={spanStyle} >Company</span>
<input
data-testid="name" id="name" name="name"
value={customer.name}
onChange={formValueOnChange}
/>
</div>
<div>
<span style={spanStyle} >CEO</span>
<input
data-testid="ceo" id="ceo" name="ceo"
value={customer.ceo}
onChange={formValueOnChange}
/>
</div>
<div>
<button name="get" onClick={async () => await fetchCustomer(customer.id)}>Get customer</button>
<button name="clear" onClick={clear}>Clear fields</button>
<button name="save" onClick={async () => await postCustomer()}>Save</button>
<button name="delete" onClick={async () => await deleteCustomer(customer.id)}>Delete</button>
</div>
</div>
)
}
/src/customerdetails/useCustomerDetails.js
This custom hook encapsulates state, and routines interfacing the rest api. Note that data for the input fields is using the customer state, which will contain an object after data is fetched.
import { useEffect, useState } from "react"
export const useCustomerDetails = (props) => {
const [isloading, setIsloading] = useState(false)
const [errorMessage, setErrorMessage] = useState('')
const [message, setMessage] = useState('')
const [customer, setCustomer] = useState({})
const initializeStatus = () => {
setIsloading(true)
setErrorMessage('')
setMessage('')
}
const fetchCustomer = async (id) => {
initializeStatus()
const fetchResult = await fetch(`/customer/${id}`, { method: 'GET' })
const result = await fetchResult.json()
if (fetchResult.ok) {
setCustomer({ ...result })
setMessage('Customer loaded from rest api')
setIsloading(false)
return;
} else {
setIsloading(false)
setErrorMessage(result.errorMessage)
}
}
const postCustomer = async (customer) => {
initializeStatus()
const fetchResult = await fetch('/customer', {
method: 'POST',
body: JSON.stringify(customer)
})
const result = await fetchResult.json()
if (fetchResult.ok) {
setCustomer({ ...result })
setMessage(result.message)
setIsloading(false)
return;
} else {
setIsloading(false)
setErrorMessage(result.errorMessage)
}
}
const deleteCustomer = async (id) => {
initializeStatus()
const fetchResult = await fetch(`customer/${id}`, {
method: 'DELETE',
})
const result = await fetchResult.json()
if (fetchResult.ok) {
setCustomer({ ...result })
setMessage('Customer loaded from rest api')
setIsloading(false)
return;
} else {
setIsloading(false)
setErrorMessage(result.errorMessage)
}
}
useEffect(() => {
fetchCustomer(props.customerId);
}, [props.customerId])
const formValueOnChange = (e) => {
console.log(e.target.name, e.target.value)
setCustomer({ ...customer, [e.target.name]: e.target.value })
}
const clear = () => setCustomer({ id: '', name: '', ceo: '' })
return { isloading, errorMessage, message, customer, fetchCustomer, clear, postCustomer, deleteCustomer, formValueOnChange }
}
To run the application, you enter “npm start” in the terminal, point your browser to “localhost:3000”, press F12 to display develeoper tools, click on the “Network” tab. You will se something like below:
You can inspect the mocked network requests (“[MSW]”), and they are just like ordinary fetch requests.
In my company Ulriken Consulting, we all work as consultants. We have several meetup groups, were we meet to discuss and play with technologies. I co-run the Front-End group where we explore frontend stuff. This blog was theme for the august 2021 meetup.
We used this content as a starting point. Paired up for pairprogramming using VsCode Live Share. Task is to add new features to the code, a new list, and a new detailspage of choice (Product or something else).
You will find the project at Github: https://github.com/magneka/MockServiceWorkerMeetup