4. RESTful CRUD API
You now have the skills to build a simple server application. But it can only perform one of the four CRUD capabilities.
In this lesson, you will learn how to add crate, update, and delete endpoints and best practices for creating a "RESTful API".
Table of Contents:
Terms
RESTful API — an API that conforms to the design principles of representational state transfer (REST).
express.json()Middleware — parses JSON data from the request and adds it toreq.bodyin the controller.Route Parameters — named URL segments that are used to capture the values specified at their position in the URL. The captured values are populated in the
req.paramsobject.HTTP Response Codes:
201— Success: Created204— Success: No Content400— Client Error: Invalid Request404— Client Error: Not Found500— Server Error: Internal Server Error503— Server Error: Unavailable
Introduction: Client → Server → Client → Server
Clone down the provided repository and cd into the starter folder. There, you will see a frontend and server provided for you. Run both applications and you'll see it is a simple application that shows a list of fellows and their unique IDs.

This application represents an essential pattern that we've seen a few times now:
The server sends the client a frontend application
The frontend application provides the user with buttons or forms that send requests back to the server
The server responds with data
The frontend renders that data
This back and forth pattern between a server application and the frontend application (both from the same origin) underlies virtually every web application that you've ever used!
CRUD Applications
This example application is quite limited in its capabilities. It only interact with its server to read data using GET HTTP requests.
Take a look at the frontend adapters and the server endpoints/controllers and you'll see familiar code:
// The Home component uses this adapter to render allFellows
export const getAllFellows = async () => {
const [allFellows, error] = await handleFetch('/api/fellows')
return [allFellows, error];
}// A mock "database" of fellows
const fellows = [
{ name: 'Carmen', id: getId() },
{ name: 'Reuben', id: getId() },
{ name: 'Maya', id: getId() },
];
const serveFellows = (req, res) => {
res.send(fellows);
}
app.get('/api/fellows', serveFellows);But think about an application like Google Calendar where you can create an account, create new events, edit events, and delete them too! Applications like these perform all of the CRUD operations.

That is, both their server and frontend are set up in such a way that enable users to create, read, update, and delete data via HTTP requests:
Create —
POSTHTTP requestsRead —
GETHTTP requestsUpdate —
PATCH/PUTHTTP requests (PUTmeans replace the whole resource,PATCHmeans change just a part of the resource)Delete —
DELETEHTTP requests
Let's look at how we can add create, update, and delete capabilities to our application!
Creating Data with POST Requests and Endpoints
To enable our frontend application to create data, we will add POST endpoints on our server and send POST requests from the frontend, often triggered by a form.
Since POST requests are requests to create data, we need to send data in the body of the request. On the frontend, this means including an options object with our fetch.
export const createFellow = async (fellowName) => {
const options = {
method: "POST",
headers: { "Content-type": "application/json" },
body: JSON.stringify({ fellowName }) // make sure this object matches req.body on the server
}
const [newFellow, error] = await handleFetch(`/api/fellows/`, options);
return [newFellow, error];
}On our server, we expect JSON data to be included with the request body. The express.json() middleware parses JSON data from the request and adds it to req.body in the controller. With the data retrieved, we can create a new fellow object, add it to our "database" and send a response (or an error).
To register the controller to respond to POST requests, we use the app.post method instead of app.get
// Middleware parses JSON from the request and adds it to req.body
app.use(express.json());
const createFellow = (req, res) => {
// make sure this object matches the options.body on the frontend
const { fellowName } = req.body;
if (!fellowName) {
// 400 means "invalid request"
return res.status(400).send({ message: "Invalid Name"});
}
const newFellow = {
name: fellowName,
id: getId()
}
fellows.push(newFellow)
// 201 means "resource created successfully"
res.status(201).send(newFellow);
}
// Use app.post instead of app.get
app.post('/api/fellows', createFellow);With the server controllers built on the backend and the adapter built on the frontend, we can easily test our code by hard-coding a call to the adapter:
export const createFellow = async (fellowName) => {
const options = {
method: "POST",
headers: { "Content-type": "application/json" },
body: JSON.stringify({ fellowName });
}
const [newFellow, error] = await handleFetch(`/api/fellows/`, options);
return [newFellow, error];
}
// Just invoke the function to test it out! You should see the server receive the request
createFellow('Winnie the Pooh');With this code fully tested from the frontend and backend, we can now incorporate it into our React components! See if you can figure out how to trigger the createFellow adapter within the Home component.
Making A RESTful API
We now have an application that can create new fellows and read the full list of fellows that have been created! Try clicking on a fellow in the list and, thanks to React Router, we are redirected to the FellowDetails page.
Now, we now want to add the following features to the FellowDetails page
View the details (name and id) of the chosen fellow
Update the name of the chosen fellow
Delete the chosen fellow from the database
Before we go into the code for adding these features, we need to talk about designing our endpoints according to REST (Representational State Transfer).
Pop Quiz: Guess the action that each of these requests will perform!
If you guessed these correctly, there is a good reason why! The endpoints are intuitive to understand thanks to the best practices encouraged by REST.
Core Principles of REST
restfulapi.net puts it best:
REST is an acronym for REpresentational State Transfer and an architectural style for distributed hypermedia systems. Roy Fielding first presented it in 2000 in his famous dissertation. Since then, it has become one of the most widely used approaches for building web-based APIs (Application Programming Interfaces).
REST is not a protocol or a standard, it is an architectural style. During the development phase, API developers can implement REST in a variety of ways.
Like the other architectural styles, REST also has its guiding principles and constraints. These principles must be satisfied if a service interface is to be referred to as RESTful.
The guiding principles of REST can be found in detail in the website above. Here, we've provided a few easy ways to ensure you are building a RESTful API:
Requests Should be Statelessness: Each request should contain all the information needed by the client for the current. The server doesn't store the current client state.
For example, if a user selects a filter that is applied to a
GETrequest, that filter must be provided with every request. The server should not be expected to remember the client's state.
Endpoints Describe Resources, Not Actions: Use plural nouns (e.g.,
/api/users,/api/posts) instead of verbs (e.g.,/api/getUser,/api/createPost) for endpoint URLs.A common exception to this rule is
/api/loginand/api/logout
HTTP Methods Matter:
GET– Retrieve dataPOST– Create dataPUT / PATCH– Update dataDELETE– Remove data
Endpoints Should Use Proper Status Codes (e.g., 200 OK, 201 Created, 400 Bad Request, 404 Not Found, 500 Internal Server Error)
URLs Should Indicate a Clear Hierarchy of Resources:
Use IDs to get individual resources:
/api/users/123Nest resources to indicate ownership:
/api/users/123/posts/456Avoid deep nesting that becomes hard to manage.
These principles help our API become "RESTful". But keep in mind that these are just guidelines that help make an API more intuitive and predictable for the client. Providing clear documentation will always be the best way to ensure your API is used properly.
Route Parameters
In the request GET /api/fellows/3, the value 3 indicates that I want to get the fellow with the ID 3. To generalize the id value in this request endpoint, we define the endpoint like so:
GET /api/fellows/:idIn this more generalized endpoint URL, the :id portion is called a route parameter—a placeholder in the endpoint for a value provided by the client.
Find By, Update, and Delete
With route parameters, we can now add these endpoints to our server
GET /api/fellows/:idusingapp.get()PATCH /api/fellows/:idusingapp.patch()DELETE /api/fellows/:idusingapp.delete()
In each of the corresponding controllers, we can access the id route parameter's value via the req.params object.
When fetching a single fellow, we use the id route parameter to find the fellow with the matching id in our "database":
const serveFellow = (req, res) => {
// Make sure the property name matches the route parameter below
const { id } = req.params;
// Keep in mind, route parameters are stored as strings.
const fellow = fellows.find(fellow => fellow.id === Number(id));
if (!fellow) {
// 404 means "Resource Not Found"
return res.status(404).send({
message: `No fellow with the id ${id}`
});
}
res.send(fellow);
};
// req.params.id will hold the value of the requested id
app.get('/api/fellows/:id', serverFellow)A PATCH request is similar to a POST request in that we expect the request body to include JSON data. Here, we expect a new fellowName to be provided. We again use the id route parameter to find the fellow with the matching id in our "database" and then update the name property.
const updateFellow = (req, res) => {
const { fellowName } = req.body;
if (!fellowName) {
return res.status(400).send({ message: "Invalid Name" });
}
const { id } = req.params;
const updatedFellow = fellows.find(fellow => fellow.id === Number(id));
if (!updatedFellow) {
return res.status(404).send({
message: `No fellow with the id ${id}`
});
}
updatedFellow.name = fellowName;
res.send(updatedFellow);
}
app.patch('/api/fellows/:id', updateFellow);Here, we use the id route parameter to find the index of the fellow with the matching id in our "database" so that we can splice that index out of the array.
const deleteFellow = (req, res) => {
const { id } = req.params;
const fellowIndex = fellows.findIndex((fellow) => fellow.id === Number(id));
if (fellowIndex < 0) {
return res.status(404).send({
message: `No fellow with the id ${id}`
});
}
fellows.splice(fellowIndex, 1);
// 204 means "no content" - the request was successful but there's no content to send back
res.sendStatus(204);
}
app.delete('/api/fellows/:id', deleteFellow);In our frontend, we can create adapters for each endpoint:
Sending a GET /api/fellows:id request is straightforward!
export const getFellowById = async (id) => {
const [fellow, error] = await handleFetch(`/api/fellows/${id}`);
return [fellow, error];
}Sending a PATCH /api/fellows:id request requires us to include the id in the endpoint URL and the new fellowName in the options.body, along with the appropriate options.method and options.headers values.
export const updateFellowName = async (id, fellowName) => {
const options = {
method: "PATCH",
headers: { "Content-type": "application/json" },
body: JSON.stringify({ fellowName })
};
const [updatedFellow, error] = await handleFetch(`/api/fellows/${id}`, options);
return [updatedFellow, error];
}Sending a DELETE /api/fellows:id request requires us to include the id in the endpoint URL and the appropriate options.method value.
export const deleteFellow = async (id) => {
const options = {
method: "DELETE",
};
const [success, error] = await handleFetch(`/api/fellows/${id}`, options);
return [success, error];
}Last updated