Marcy Lab School Docs
  • Welcome
  • Student Guidelines & Policies
    • Student Handbook
    • AI Policy
    • Academic Calendar
  • Environment Setup
    • Local Environment Setup - Mac
    • Local Environment Setup - Windows
    • GitHub Setup
    • Postgres Setup
  • How-Tos
    • How To Code at Marcy: Code Style Guide
    • How to Do Short Response and Coding Assignments
    • How to Debug
    • How to PEDAC
    • How to Create A GitHub Organization and Scrumboard
    • How to Create Projects with Vite
    • How to Deploy on GitHub Pages
    • How to Deploy on Render
    • How to Test your API with Postman
  • Mod 0 - Command Line Interfaces, Git, and GitHub
    • Overview
    • 1. Command Line Interfaces
    • 2. Git & GitHub
    • 3. Git Pulling & Merging
    • 4. Git Branching & PRs
  • Mod 1 - JavaScript Fundamentals
    • Overview
    • 1. Intro to Programming
    • 2. Errors
    • 3. Node & Node Modules
    • 4. Variables, Functions & String Methods
    • 5. Control Flow, typeof, and Math
    • 6. Loops
    • 7. Arrays
    • 8. Objects
    • 9. Higher Order Functions: Callbacks
    • 10. Higher Order Functions: Array Methods
    • 11. Regex
  • Mod 2 - HTML, CSS & the DOM
    • Overview
    • 1. HTML
    • 2. CSS
    • 3. Accessibility (a11y)
    • 4. The Document Object Model (DOM) API
    • 5. Events
    • 6. Forms
    • 7. The Box Model and Positioning
    • 8. Flexbox
    • 9. Grid & Media Queries
    • 10. ESModules
    • 11. Vite
    • 12. LocalStorage
  • Mod 3 - Async & APIs
    • Overview
    • 1. Promises
    • 2. Fetch
    • 3. Building a Fetching App
    • 4. Async & Await
    • 5. A Generic Fetch Handler
  • Mod 4 - Project Week!
    • Important How Tos and Guides
      • How to Create a GitHub Organization and Scrum Board
      • How To Start a Project with Vite
      • How To Deploy a Project with GitHub Pages
    • Project Week Overview
    • Agile Methodologies
    • Deliverables & Milestones
    • Technical Requirements Checklist
    • Free API List
    • Collaborative GitHub
  • Mod 5 - Object-Oriented Programming
    • Overview
    • 1. Intro to OOP, Encapsulation, Factory Functions, and Closure
    • 2. Classes
    • 3. Private & Static
    • 4. UML Diagrams & Has Many/Belongs To Relationships
    • 5. Challenge: Implementing Has Many/Belongs To
    • 6. Inheritance
    • 7. Polymorphism
    • 8. Review and Practice
    • MDN: Object Prototypes
  • Mod 6 - Data Structures & Algorithms
    • Overview
    • Important How Tos and Guides
      • How to Debug
      • How to PEDAC
    • 1. Nodes & Linked Lists
    • 2. Singly & Doubly Linked Lists
    • 3. Stacks & Queues
    • 4. Recursion
    • 5. Trees
  • Mod 7 - React
    • Overview
    • Important How Tos and Guides
      • How to Create Projects with Vite
      • How to Deploy on GitHub Pages
    • 1. Intro to React
    • 2. Events, State, and Forms
    • 3. Fetching with useEffect
    • 4. React Router
    • 5. Building a Flashcards App
    • 6. React Context
    • 7. Global Context Pattern
  • Mod 8 - Backend
    • Overview
    • Important How Tos and Guides
      • How to Deploy on Render
      • How to Test your API with Postman
      • Postgres Setup
    • 1. Intro to Express
    • 2. Building a Static Web Server with Middleware
    • 3. Securing API Keys and Environment Variables
    • 4. RESTful CRUD API
    • 5. Model-View-Controller Architecture
    • 6. SQL and Databases
    • 7. JOIN (Association) SQL Queries
    • 8. Knex
    • 9. Your First Fullstack App!
    • 10. Migrations & Seeds
    • 11. Schema Design & Normalization
    • 12. Hashing Passwords with Bcrypt
    • 13. React Express Auth Template Overview
  • Mod 9 - Civic Tech Hackathon
    • Overview
    • Rubric
  • Mod 10 - Capstone
    • Overview
Powered by GitBook
On this page
  • Terms
  • Introduction: Client → Server → Client → Server
  • CRUD Applications
  • Creating Data with POST Requests and Endpoints
  • Making A RESTful API
  • Core Principles of REST
  • Route Parameters
  • Find By, Update, and Delete
  1. Mod 8 - Backend

4. RESTful CRUD API

Previous3. Securing API Keys and Environment VariablesNext5. Model-View-Controller Architecture

Last updated 1 month ago

Follow along with code examples !

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 to req.body in 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.params object.

  • HTTP Response Codes:

    • 201 — Success: Created

    • 204 — Success: No Content

    • 400 — Client Error: Invalid Request

    • 404 — Client Error: Not Found

    • 500 — Server Error: Internal Server Error

    • 503 — 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:

frontend/src/adapters/fellowAdapters.js
// The Home component uses this adapter to render allFellows
export const getAllFellows = async () => {
  const [allFellows, error] = await handleFetch('/api/fellows')
  return [allFellows, error];
}
server/index.js
// 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 — POST HTTP requests

  • Read — GET HTTP requests

  • Update — PATCH/PUT HTTP requests (PUT means replace the whole resource, PATCH means change just a part of the resource)

  • Delete — DELETE HTTP 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.

frontend/src/adapters/fellowAdapters.js
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

server/index.js
// 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:

frontend/src/adapters/fellowAdapters.js
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.

Solution

The Home component already includes a contolled form that updates the newFellowName state value. When handling the form submission, we can use that state value to send our createFellow request. Then, to update the list with the new data in our "database", we send another getAllFellows() request and update the fellows state.

frontend/src/pages/Home.jsx
const handleCreateFellow = async (e) => {
  e.preventDefault();
  console.log(`Creating fellow: ${newFellowName}`);

  // use the createFellow adapter with the form input
  createFellow(newFellowName);
  
  // re-fetch all the fellows and update the state to re-render the list
  const [allFellows, error] = await getAllFellows();
  setFellows(allFellows);

  setNewFellowName('');
}

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!

GET /api/fellows/3

Get the fellow with the id 3

PATCH /api/fellows/1

Update the fellow with the id 1

DELETE /api/fellows/2

Delete the fellow with the id 2

POST /api/fellows/2/bio

Create a bio for the fellow with the id 2

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

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:

  1. 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 GET request, that filter must be provided with every request. The server should not be expected to remember the client's state.

  2. 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/login and /api/logout

  3. HTTP Methods Matter:

    • GET – Retrieve data

    • POST – Create data

    • PUT / PATCH – Update data

    • DELETE – Remove data

  4. Endpoints Should Use Proper Status Codes (e.g., 200 OK, 201 Created, 400 Bad Request, 404 Not Found, 500 Internal Server Error)

  5. URLs Should Indicate a Clear Hierarchy of Resources:

    • Use IDs to get individual resources: /api/users/123

    • Nest resources to indicate ownership: /api/users/123/posts/456

    • Avoid 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/:id

In 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/:id using app.get()

  • PATCH /api/fellows/:id using app.patch()

  • DELETE /api/fellows/:id using app.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];
}

puts it best:

restfulapi.net
here
Terms
Introduction: Client → Server → Client → Server
CRUD Applications
Creating Data with POST Requests and Endpoints
Making A RESTful API
Core Principles of REST
Route Parameters
Find By, Update, and Delete
An application that renders a list of fellows fetched from the server
In Google Calendar, the application provides a button for the user to press that sends a PATCH request to the server.