When developing an application that relies on an API that has not yet been created or finished, we run into a major chicken-and-egg problem. How can we make an application that talks to something that doesn't yet exist? If we're doing this in JavaScript, we can start by creating a facade for our API.
What's a facade? All this means is that we write our own objects or functions that we call instead of directly making API calls from our code. Instead of making those API calls directly, we use our objects or functions, which we'll use to decide how to talk to the API (or provide us with mock data.)
For the rest of this post, I'm going to assume we're using React and building our code with create-react-app. These ideas apply whether that's the case or not, but I'm going to rely on process.env.NODE_ENV
existing, which is provided in create-react-app applications, since they are built with Babel and Node.
Option 0: hard-code our data
I choose this option whenever I start a new React application, but only use it for a short while, and I recommend we only use it at the very beginning to get something on the page. We can set a default state and display the results. Imagine we're building a simple application to create and display notes. We could write something like the following for a NoteList
component:
function NoteList ({authToken}) {
const [notes, setNotes] = useState([
{ id: 1, body: 'Note 1', color: 'blue' },
{ id: 2, body: 'Note 2', color: 'yellow' },
{ id: 3, body: 'Note 3', color: 'pink' }
])
// render our notes
}
Option 1: use mock data
This option is great early on, especially if we don't even have a specification for the API. We might not know what the data we'll get back looks like, but we know what data we'll need, so we can start there. For our note application, we'll need to log in, get a list of notes, and create new notes.
We could create the following functions in a file called api.js
.
const notes = [
{ id: 1, body: 'Note 1', color: 'blue' },
{ id: 2, body: 'Note 2', color: 'yellow' },
{ id: 3, body: 'Note 3', color: 'pink' }
]
let lastNoteId = 3
export function getAuthToken (username, password) {
return 'testtoken'
}
export function getNotes (authToken) {
return notes
}
export function storeNote (authToken, note) {
lastNoteId += 1
note.id = lastNoteId
notes.push(note)
return note
}
This won't quite work, though. Depending on our client for making AJAX calls, we might be dealing with callbacks or promises. Again, I'm going to make an assumption that you're using Axios, which uses promises. In order to return promises from our code, we'll need to change it a bit.
export function getAuthToken (username, password) {
return new Promise((resolve, reject) => {
resolve({
token: 'testtoken'
})
})
}
export function getNotes (authToken) {
return new Promise((resolve, reject) => {
resolve({ notes: notes })
})
}
export function storeNote (authToken, note) {
lastNoteId += 1
note.id = lastNoteId
notes.push(note)
return new Promise((resolve, reject) => {
resolve(note)
})
}
Now we have a promise-based interface, which will let us use these in the same way we'll eventually deal with real API calls. Here's an example of a NoteList
React component we might write:
import { getNotes } from './api.js'
function NoteList ({authToken}) {
const [notes, setNotes] = useState([])
useEffect(() => {
getNotes(authToken).then(data => setNotes(data.notes))
})
// render our notes
}
We can go further with this. Our API will error if we don't give it the correct data -- for example, if we try to log in with an incorrect username or password. We can make our getAuthToken
function work the same way. This will let us test out failure states in our application.
export function getAuthToken (username, password) {
return new Promise((resolve, reject) => {
if (username === "testuser" && password === "password") {
resolve({
token: 'testtoken'
})
} else {
reject({
message: 'unauthorized'
})
}
})
}
We will want to add failure states to each of our API functions to be able to test all states.
Option 2: use a mock API
A mock API is a set of API endpoints that return data that looks like our eventual real API, but isn't backed by a database and doesn't persist data. There are a lot of tools to make mock APIs out there, but most of them are hosted and cost money. A free tool that runs on your local machine is Mockoon. With this tool, you can create endpoints with failure and success states. I'm not going to go through how to set up Mockoon here, but let's look at how we might use it with our facade.
Axios has the ability to create an instance with defaults, which will help us out. Here's how we might use it:
const mockAPI = 'http://localhost:3000/api'
const productionAPI = 'https://example.org/api'
const request = axios.create({
baseURL: process.env.NODE_ENV === 'production' ? productionAPI : mockAPI
})
export function getAuthToken (username, password) {
return request.post('/auth/token/login/', {
username: username, password: password
}).then(res => res.data)
}
export function getNotes (authToken) {
return request.get('/notes/', {
headers: { Authorization: authToken }
}).then(res => res.data)
}
This code will look at the value of process.env.NODE_ENV
, which will be set to "development" when we run it via npm start
and "production" when we run npm run build
, and use that to decide whether requests should go to our mock API or our production API. As long as our mock API returns data in the same shape as our production API, this should let us develop our front-end application before the API is built.
Top comments (0)