Skip to content

Mastering REST APIs: Build, Test, and Deploy with Bruno & Render

REST APIs stand for Representational State Transfer, and they are the backbone of modern software development, enabling communication between systems.

For example, when you’re reading this blog on the internet, your browser (or app) sends a request to the Bruno blog website. The website then responds with a web page listing the blogs. Behind the scenes, your client (mobile, desktop, etc.) is calling an API to access this website, and the API serves the response back to you

What Exactly is an API? The Digital Messenger

Imagine you're at a restaurant. You don't go into the kitchen to cook your meal; instead, you tell the waiter what you want, and they relay your order to the kitchen. The kitchen prepares it, and the waiter brings it back to you.

In the digital realm, an API (Application Programming Interface) acts like that waiter. It's a set of rules and protocols that allows different software applications to communicate with each other.

Screenshot 2025-09-23 at 4.40.40 PM

What is a REST API?

While there are various types of APIs, one of the most popular and widely adopted styles is REST (Representational State Transfer). A REST API adheres to a set of architectural constraints that make it simple, scalable, and stateless.

  • Client-Server Architecture: The client (e.g., your web browser, mobile app) is separate from the server (where the API lives).
  • Statelessness: Each request from a client to the server must contain all the information needed to understand the request. The server doesn't store any client context between requests.
  • Cacheable: Responses can be defined as cacheable or non-cacheable to improve performance.
  • Layered System: A client typically can't tell whether it's connected directly to the end server or to an intermediary along the way.

Building Your REST API

To build a REST API, we will use Node.js and Express.js as the backend framework.

Your API will:

  1. Define Endpoints: Create routes that map to specific URLs and HTTP methods.
  2. Handle Requests: When a request comes in, parse its method, URL, and any data in the body or headers.
  3. Interact with Data: Often, this involves communicating with a database to store or retrieve information.
  4. Send Responses: Return data (usually JSON) and an appropriate HTTP status code (e.g., 200 OK, 201 Created, 404 Not Found, 500 Internal Server Error).

Example: Simple Books REST API (Express.js)

Here’s how you might set up a simple CRUD (Create, Read, Update, Delete) API for managing books using Express.js. We'll break down the code into sections for clarity and then provide the full runnable example.

1. Configuration & Setup Block

This section handles the initial setup of our Express.js application, including module imports, middleware, and our temporary in-memory data store.

// Import necessary modules
const express = require('express');
const { v4: uuidv4 } = require('uuid'); // For generating unique IDs

// Initialize the Express application
const app = express();
const PORT = process.env.PORT || 3000;

// Middleware to parse JSON request bodies
// This allows Express to read JSON data sent in POST, PUT, PATCH requests
app.use(express.json());

// In-memory "database" for our books
// In a real application, this would be a persistent database (e.g., PostgreSQL, MongoDB)
let books = [
  {
    id: uuidv4(),
    title: 'The Lord of the Rings',
    author: 'J.R.R. Tolkien',
    publicationYear: 1954,
  },
  {
    id: uuidv4(),
    title: 'Pride and Prejudice',
    author: 'Jane Austen',
    publicationYear: 1813,
  },
  {
    id: uuidv4(),
    title: '1984',
    author: 'George Orwell',
    publicationYear: 1949,
  },
];

2. GET API Blocks (Read Operations)

These endpoints handle requests to retrieve information about books.

// --- GET API Endpoints (Read Operations) ---

// GET /api/books - Get all books
// Returns a list of all books in our collection.
app.get('/api/books', (req, res) => {
  res.status(200).json(books);
});

// GET /api/books/:id - Get a single book by ID
// Retrieves a specific book using its unique ID from the URL parameters.
app.get('/api/books/:id', (req, res) => {
  const { id } = req.params; // Extract the ID from the URL
  const book = books.find((b) => b.id === id); // Find the book in our 'database'

  if (!book) {
    // If no book is found, return a 404 Not Found error
    return res.status(404).json({ message: `Book with ID '${id}' not found.` });
  }

  // If found, return the book with a 200 OK status
  res.status(200).json(book);
});

3. POST API Block (Create Operation)

This endpoint handles requests to add a new book to our collection.

// --- POST API Endpoint (Create Operation) ---

// POST /api/books - Create a new book
// Adds a new book to the collection based on data provided in the request body.
app.post('/api/books', (req, res) => {
  const { title, author, publicationYear } = req.body; // Extract data from the request body

  // Basic validation: ensure title and author are provided
  if (!title || !author) {
    return res
      .status(400) // 400 Bad Request if essential data is missing
      .json({ message: 'Title and author are required fields.' });
  }

  // Create a new book object with a unique ID
  const newBook = {
    id: uuidv4(), // Generate a unique ID for the new book
    title,
    author,
    publicationYear: publicationYear || null, // Allow publicationYear to be optional
  };

  books.push(newBook); // Add the new book to our in-memory array
  res.status(201).json(newBook); // 201 Created: Indicates successful resource creation
});

4. PUT API Block (Full Update Operation)

This endpoint handles requests to completely replace an existing book's data.

// --- PUT API Endpoint (Full Update Operation) ---

// PUT /api/books/:id - Update a book (full replacement)
// Replaces an entire book's data identified by its ID with the new data from the request body.
app.put('/api/books/:id', (req, res) => {
  const { id } = req.params; // Get the ID from the URL
  const { title, author, publicationYear } = req.body; // Get new data from the request body

  // Basic validation for the replacement data
  if (!title || !author) {
    return res
      .status(400)
      .json({ message: 'Title and author are required fields for PUT.' });
  }

  // Find the index of the book to be updated
  const bookIndex = books.findIndex((b) => b.id === id);

  if (bookIndex === -1) {
    // If book not found, return 404 Not Found
    return res
      .status(404)
      .json({ message: `Book with ID '${id}' not found for update.` });
  }

  // Create an updated book object, keeping the original ID
  const updatedBook = {
    id, // Keep the existing ID
    title,
    author,
    publicationYear: publicationYear || null,
  };

  books[bookIndex] = updatedBook; // Replace the old book with the updated one
  res.status(200).json(updatedBook); // 200 OK: Indicates successful update
});

5. PATCH API Block (Partial Update Operation)

This endpoint handles requests to partially modify an existing book's data.

// --- PATCH API Endpoint (Partial Update Operation) ---

// PATCH /api/books/:id - Partially update a book
// Modifies only the specified fields of an existing book.
app.patch('/api/books/:id', (req, res) => {
  const { id } = req.params; // Get the ID from the URL
  const updates = req.body; // Get partial update data from the request body

  // Find the index of the book to be updated
    const bookIndex = books.findIndex((b) => b.id === id);

  if (bookIndex === -1) {
    // If book not found, return 404 Not Found
    return res
      .status(404)
      .json({ message: `Book with ID '${id}' not found for partial update.` });
  }

  // Apply updates: use spread operator to merge existing book data with new updates
  books[bookIndex] = { ...books[bookIndex], ...updates };

  res.status(200).json(books[bookIndex]); // 200 OK: Return the updated book
});

6. DELETE API Block (Delete Operation)

This endpoint handles requests to remove a book from the collection.

// --- DELETE API Endpoint (Delete Operation) ---

// DELETE /api/books/:id - Delete a book
// Removes a book from the collection identified by its ID.
app.delete('/api/books/:id', (req, res) => {
  const { id } = req.params; // Get the ID from the URL

  const initialLength = books.length; // Store initial length to check if a book was actually removed
  books = books.filter((b) => b.id !== id); // Filter out the book to be deleted

  if (books.length === initialLength) {
    // If no book was removed (length didn't change), it means the ID wasn't found
    return res
      .status(404)
      .json({ message: `Book with ID '${id}' not found for deletion.` });
  }

  // 204 No Content: Indicates successful deletion with no response body
  res.status(204).send();
});

Complete Code: Books REST API (All-in-One)

Here's the full code for the Express.js Books REST API, which you can save as app.js and run.

// Import necessary modules
const express = require('express');
const { v4: uuidv4 } = require('uuid'); // For generating unique IDs

// Initialize the Express application
const app = express();
const PORT = process.env.PORT || 3000;

// Middleware to parse JSON request bodies
// This allows Express to read JSON data sent in POST, PUT, PATCH requests
app.use(express.json());

// In-memory "database" for our books
// In a real application, this would be a persistent database (e.g., PostgreSQL, MongoDB)
let books = [
  {
    id: uuidv4(),
    title: 'The Lord of the Rings',
    author: 'J.R.R. Tolkien',
    publicationYear: 1954,
  },
  {
    id: uuidv4(),
    title: 'Pride and Prejudice',
    author: 'Jane Austen',
    publicationYear: 1813,
  },
  {
    id: uuidv4(),
    title: '1984',
    author: 'George Orwell',
    publicationYear: 1949,
  },
];

// --- API Endpoints ---

// GET /api/books - Get all books
// Returns a list of all books in our collection.
app.get('/api/books', (req, res) => {
  res.status(200).json(books);
});

// GET /api/books/:id - Get a single book by ID
// Retrieves a specific book using its unique ID from the URL parameters.
app.get('/api/books/:id', (req, res) => {
  const { id } = req.params; // Extract the ID from the URL
  const book = books.find((b) => b.id === id); // Find the book in our 'database'

  if (!book) {
    // If no book is found, return a 404 Not Found error
    return res.status(404).json({ message: `Book with ID '${id}' not found.` });
  }

  // If found, return the book with a 200 OK status
  res.status(200).json(book);
});

// POST /api/books - Create a new book
// Adds a new book to the collection based on data provided in the request body.
app.post('/api/books', (req, res) => {
  const { title, author, publicationYear } = req.body; // Extract data from the request body

  // Basic validation: ensure title and author are provided
  if (!title || !author) {
    return res
      .status(400) // 400 Bad Request if essential data is missing
      .json({ message: 'Title and author are required fields.' });
  }

  // Create a new book object with a unique ID
  const newBook = {
    id: uuidv4(), // Generate a unique ID for the new book
    title,
    author,
    publicationYear: publicationYear || null, // Allow publicationYear to be optional
  };

  books.push(newBook); // Add the new book to our in-memory array
  res.status(201).json(newBook); // 201 Created: Indicates successful resource creation
});

// PUT /api/books/:id - Update a book (full replacement)
// Replaces an entire book's data identified by its ID with the new data from the request body.
app.put('/api/books/:id', (req, res) => {
  const { id } = req.params; // Get the ID from the URL
  const { title, author, publicationYear } = req.body; // Get new data from the request body

  // Basic validation for the replacement data
  if (!title || !author) {
    return res
      .status(400)
      .json({ message: 'Title and author are required fields for PUT.' });
  }

  const bookIndex = books.findIndex((b) => b.id === id); // Find the index of the book to be updated

  if (bookIndex === -1) {
    // If book not found, return 404 Not Found
    return res
      .status(404)
      .json({ message: `Book with ID '${id}' not found for update.` });
  }

  // Create an updated book object, keeping the original ID
  const updatedBook = {
    id, // Keep the existing ID
    title,
    author,
    publicationYear: publicationYear || null,
  };

  books[bookIndex] = updatedBook; // Replace the old book with the updated one
  res.status(200).json(updatedBook); // 200 OK: Indicates successful update
});

// PATCH /api/books/:id - Partially update a book
// Modifies only the specified fields of an existing book.
app.patch('/api/books/:id', (req, res) => {
  const { id } = req.params; // Get the ID from the URL
  const updates = req.body; // Get partial update data from the request body

  const bookIndex = books.findIndex((b) => b.id === id); // Find the index of the book to be updated

  if (bookIndex === -1) {
    // If book not found, return 404 Not Found
    return res
      .status(404)
      .json({ message: `Book with ID '${id}' not found for partial update.` });
  }

  // Apply updates: use spread operator to merge existing book data with new updates
  books[bookIndex] = { ...books[bookIndex], ...updates };

  res.status(200).json(books[bookIndex]); // 200 OK: Return the updated book
});

// DELETE /api/books/:id - Delete a book
// Removes a book from the collection identified by its ID.
app.delete('/api/books/:id', (req, res) => {
  const { id } = req.params; // Get the ID from the URL

  const initialLength = books.length; // Store initial length to check if a book was actually removed
  books = books.filter((b) => b.id !== id); // Filter out the book to be deleted

  if (books.length === initialLength) {
    // If no book was removed (length didn't change), it means the ID wasn't found
    return res
      .status(404)
      .json({ message: `Book with ID '${id}' not found for deletion.` });
  }

  // 204 No Content: Indicates successful deletion with no response body
  res.status(204).send();
});

// --- Server Activation ---

// Start the server
app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});

To run this example, save it as `server.js` and install dependencies:

npm install express uuid
node app.js

Once the server is running, you can test it in your browser or with an API client:

http://localhost:3000/api/books

Example Response (GET /api/books):

[{
    "id": "some-uuid-1",
    "title": "The Lord of the Rings",
    "author": "J.R.R. Tolkien",
    "publicationYear": 1954
  },
  {
    "id": "some-uuid-2",
    "title": "Pride and Prejudice",
    "author": "Jane Austen",
    "publicationYear": 1813
  }
]

Now, let's talk about the tools that make this process much smoother!


Testing Your API with Bruno API Client

Before you even think about deploying, you need to rigorously test your API. This is where an API client becomes your best friend. Bruno is an open-source and local-first API testing client.

Why, Bruno?

  • Open Source: Full transparency and community-driven development.
  • Local First: Your data (collections, requests) is stored directly on your file system as plain text files (bru files). This makes version control (Git!) incredibly easy.
  • Performance: Often noted for its snappiness and lightweight nature.
  • Elegant UI: A clean and intuitive interface for creating and managing requests.
  • Collection as Code: Because everything is file-based, you can treat your API collections like code – commit them, review changes, and share easily.

How to Test REST APIs in Bruno

Bruno simplifies testing your REST APIs. You can easily create requests for each HTTP method (GET, POST, PUT, PATCH, DELETE) and define headers, body content, and even pre-request or post-response scripts for more complex scenarios.

Create request

  • Download Bruno from the official website.
  • Create a collection with the name echo-bruno.
  • Create a request and provide a URL with a name, and make sure your server is running.

Screenshot 2025-09-24 at 6.52.47 PM

Create Test

  • Go to the assert tab and write your test cases against your API endpoint.
  • (Optional) use the Test tab to write complex test cases using JavaScript code.

Screenshot 2025-09-24 at 7.02.08 PM

Send Data with the request

  • Create a request with the POST method and provide a URL and name.
  • Go to the Body section and provide the title and author values.

Screenshot 2025-09-24 at 7.06.24 PM

This is a basic example of how you can use Bruno and write your test cases against your endpoint. Imagine you have hundreds of APIs, and you want to test, check performance, add validations, etc. Then you need to have a secure testing environment like Bruno.

Try the Demo Yourself

We’ve published the full working collection on GitHub. You can either clone this or simply click the Fetch in Bruno button below!

 

 

Screenshot 2025-09-24 at 7.09.14 PM

How to Deploy REST APIs

Deploying Your API with Render

To deploy your Express.js API to a cloud service like Render, follow these general steps:

  1. Prepare your project: Ensure you have a package.json with a start script (e.g., "start": "node app.js")
  2. Version Control: Push your code to a Git repository (GitHub, GitLab, Bitbucket).
  3. Connect to Render: Log in to the Render website, create a new web service, and connect it to your Git repository.
    Screenshot 2025-09-25 at 12.53.18 PM
  4. Configure: Render will auto-detect your Node.js project. You might need to specify the Node.js version and the start command.
  5. Deploy: Render will build and deploy your application. It will automatically redeploy on future Git pushes.
    Screenshot 2025-09-25 at 12.54.44 PM

Your project is deployed now, and feel free to check out the GitHub repo with all the source code.

Conclusion

REST APIs are the cornerstone of distributed systems, offering a flexible, scalable, and widely understood approach to building web services. By leveraging frameworks like Express.js for development and API tools like Bruno for testing, you can efficiently build, test, and maintain robust APIs.

Bruno’s local-first, Git-friendly design makes it an ideal companion for backend developers focused on reliable and collaborative API workflows. Download Bruno today and streamline your REST API development!

Join our Discord server because your APIs deserve better collaboration.