Learn from experts and connect with global tech leaders at POST/CON 24. Register by March 26 to save 30%.

Learn more →
X

How to build an API

Avatar

Don’t forget to register here to attend POST/CON 24, Postman’s biggest API conference ever: April 30 to May 1, 2024 in San Francisco.

An API, or an application programming interface, is a set of code-based instructions that enable different software components to communicate and transfer data. APIs have been around for decades, but they now function as the primary building blocks of all modern applications. Whether you’re placing an order through an e-commerce store, requesting a car from a rideshare app, or ordering delivery from your favorite restaurant, you’re using APIs. API development is therefore a crucial skill for anyone who wants to break into the tech industry.

Here, we’ll show you how to build a simple REST API, with a focus on API design and implementation. We’ll also review how you can use the Postman API Platform to test the API you’ve just built. This tutorial assumes you have some basic familiarity with the Ruby programming language and the command line.

Let’s dive in.

Step 1: Design the API

The first step in the API design process is to gain clarity on what you need your API to do. For instance, an API that handles bi-directional video streaming will have very different requirements than an API that is responsible for an authentication workflow. It’s therefore crucial to understand your API’s use case and scope before choosing an architecture, protocol, and programming language.

The next step is to decide which resources are required, how their data should be formatted and structured, how they should relate to one another, and which methods should be available on their associated endpoints. For more sophisticated, production-level APIs, you’ll also want to consider things like authentication and encryption to ensure your data remains safe and secure.

It can sometimes help to create a diagram of your API’s resources and relationships. For instance, let’s pretend we run a pet daycare business, and we’d like to build an API to help us keep track of pets and their owners. We might create a diagram that looks something like this:

Step 2: Implement the API

APIs can be built using a wide range of programming languages, architectures, and protocols. For instance, REST is the most popular API architecture, but Webhooks, GraphQL, SOAP, and gRPC are quite popular, as well. Additionally, APIs can be written in almost any programming language, including Node.js, Python, Java, and Ruby. For the sake of this tutorial, we’ll use Ruby on Rails to create a simple REST API for the pet tracker app we designed above.

Why are we using Ruby on Rails?

Ruby on Rails, which is often simply called “Rails,” is a good framework to use when building your first API because it abstracts away much of the complexity of common API development tasks, such as routing and database access. It also includes a command line tool called rails generate (or rails g) that automatically creates code for the key components of an API. Rails is therefore an appropriate choice for developers of all levels and is suitable for a wide range of applications and APIs.

Before we write any code, let’s review a few Rails terms you should be familiar with:

  1. Models: Models typically correspond to database tables or collections, and they are responsible for enforcing business rules, validating data, and performing database operations. Every resource in your API will have a corresponding model.
  2. Migrations: A Rails migration is a way to manage changes to the database schema. Migrations allow developers to modify database tables, columns, and indexes without writing SQL.
  3. Controllers: A controller manages the flow of data between the client and the database. Typically, it receives input from the user, interacts with the model to retrieve or modify the data, and—in the case of full-stack Rails apps—updates the view to display the results. Note: We will only be creating an API in this tutorial, so the view is not relevant.
  4. Routes: Rails routes map URLs to actions in controllers. When the client sends a request, it is routed to a specific controller action based on the URL path and HTTP method (e.g., GET, POST, PATCH, DELETE).

How to build an API

Now that you’ve completed the “design” step, which we discussed above, it’s time to implement your API. Before we get started, it’s important for you to confirm that you have Ruby, Rails, and SQLite installed on your computer. For more information about these prerequisites, see the Rails documentation. 

Step 1: Create a new Rails application by using the rails new command from your command line:

rails new pet_tracker --api

The --api flag configures the application to use the minimal amount of middleware that is suitable for building APIs.

Step 2: Open the pet_tracker_app directory (cd pet_tracker) and generate models that correspond to the necessary resources you identified during the design stage. In our case, we’ll need a Pet model and an Owner model. An owner may have many pets, which means these two models will have a “one-to-many” relationship.

To create the models, run the following commands:

rails g model Owner name:string email:string

rails g model Pet name:string species:string breed:string age:float owner_id:integer

When you run these commands, an id field will get added to each model automatically. Crucially, the Pet model has the foreign key of owner_id, which is how pets will be connected to owners in the database.

You can finish creating the models by opening the /app/models file in your code editor and manually adding the one-to-many relationship to the models themselves. The Pet model (in the pet.rb file) should look like this:

class Pet < ApplicationRecord
    belongs_to :owner
end

The Owner model (in the owner.rb file) should look like this:

class Owner < ApplicationRecord
    has_many :pets
end

Step 3: Create tables in the database for pets and owners by running a migration with the following command:

rails db:migrate

This command will produce the following output, which indicates that the two tables were created and are ready to be accessed.

== 20230303184619 CreateOwners: migrating =====================================
-- create_table(:owners)
   -> 0.0007s
== 20230303184619 CreateOwners: migrated (0.0007s) ============================
== 20230303184648 CreatePets: migrating =======================================
-- create_table(:pets)
   -> 0.0007s
== 20230303184648 CreatePets: migrated (0.0007s) ==============================

Step 4: Open and edit the routes.rb file so that it looks like this:

Rails.application.routes.draw do
  get '/pets', to: 'pets#index'
  post '/pets', to: 'pets#create'
  get '/pets/:id', to: 'pets#show'
  patch '/pets/:id', to: 'pets#update'
  delete '/pets/:id', to: 'pets#destroy'

  get '/owners', to: 'owners#index'
  post '/owners', to: 'owners#create'
  get '/owners/:id', to: 'owners#show'
  get '/owners/:id/pets', to: 'owners#pets'
  patch '/owners/:id', to: 'owners#update'
  delete '/owners/:id', to: 'owners#destroy'
end

This code matches specific endpoints and HTTP methods to actions on each model’s controller (which we will generate in the next step). For instance, every GET request to the /pets endpoint will be routed to the #index action on the Pets controller. Additionally, every PATCH request to the /pets/:id endpoint, which requires an :id parameter to identify the specific Pet instance, will be routed to the #update action on the Pets controller.

Note that we’ve created a custom route for the Owners controller, which will enable us to access the specific pets for a given owner. We’ll add the logic for that action soon.

Step 5: Generate the controllers for your Pet and Owner models by running the following commands:

rails generate controller Pets index create show update destroy --skip-routes

rails generate controller Owners index create show update destroy --skip-routes

These commands create controller files in the /app/controller folder. We’ve included the --skip-routes flag because we’ve created our routes manually.

Step 6: While the previous step stubbed out each controller action, we are responsible for writing the actual code ourselves. The code for the Pets controller should look like this:

class PetsController < ApplicationController
  def index
    @pets = Pet.all
    render json: @pets
  end

  def create
    @pet = Pet.new(pet_params)
    
    if @pet.save
      render json: @pet, status: :created
    else
      render json: {error: 'Pet creation failed'}, status: :unprocessable_entity
    end
  end

  def show
    @pet = Pet.find(params[:id])

    if @pet
      render json: @pet, status: :ok
    else
      render json: { error: 'Pet not found' }, status: :not_found
    end
  end

  def update
    @pet = Pet.find(params[:id])

    if @pet.update(pet_params)
      render json: @pet, status: :ok
    else
      render json: { error: 'Pet update failed' }, status: :unprocessable_entity
    end
  end

  def destroy
    @pet = Pet.find(params[:id])

    if @pet.destroy
      render json: { message: 'Pet successfully deleted' }, status: :ok
    else
      render json: { error: 'Pet deletion failed' }, status: :internal_server_error
    end
  end

  private

  def pet_params
    params.permit(:name, :species, :breed, :age, :owner_id)
  end

end

There are a few things to note about the above code. First, these controller actions make use of Rails’ built-in integration with Active Record, which simplifies database interactions by abstracting away the complexities of SQL queries. For instance, .all, .find, .new, and .destroy are all Active Record methods. Additionally, we’ve included a private method called pet_params on this controller. This private method allows us to control which parameters can be passed to the Pets controller, which prevents the client from updating fields it should not be able to update (such as a given pet’s ID).

The Owners controller will follow a similar pattern:

class OwnersController < ApplicationController
  def index
    @owners = Owner.all
    render json: @owners
  end

  def create
    @owner = Owner.new(owner_params)

    if @owner.save
      render json: @owner, status: :created
    else
      render json: {error: 'Owner creation failed'}, status: :unprocessable_entity
    end
  end

  def show
    @owner = owner.find(params[:id])

    if @owner
      render json: @owner, status: :ok
    else
      render json: { error: 'Owner not found' }, status: :not_found
    end
  end

  def pets
    @pets = Owner.find(params[:id]).pets

    if @pets
      render json: @pets
    else 
      render json: { error: 'Pets not found'}, status: :not_found
    end
  end

  def update
    @owner = Owner.find(params[:id])

    if @owner.update(owner_params)
      render json: @owner
    else
      render json: { error: 'Owner update failed' }, status: :unprocessable_entity
    end
  end

  def destroy
    @owner = Owner.find(params[:id])

    if @owner.destroy
      render json: { message: 'Owner successfully deleted' }, status: :ok
    else
      render json: { error: 'Owner deletion failed' }, status: :internal_server_error
    end
  end

  private

  def owner_params
    params.permit(:name, :email)
  end

end

As we mentioned earlier, the Owners controller has a custom pets action that uses the .find method to look up the specified owner and return all of their associated pets.

Step 7: It’s now time to start a server that will run your API locally! To do so, simply run:

rails server. Your API will now be running on http://localhost:3000/.

Step 3: Test the API

Now that you’ve built your API, it’s time to run some tests to be sure everything is working as expected. While there are many ways to validate your API’s behavior and functionality, the Postman API Platform offers an intuitive, user-friendly approach to API testing that is appropriate for any API—at any scale. Here, we’ll show you how to send requests with the Postman API client to manually validate three crucial workflows that your new API is intended to support.

Before we dive in, make sure your API server is running on http://localhost:3000/. If it’s not, you can start the server by running rails server from the command line (just be sure you run that command from the API’s directory). When you’re done, you can stop the server with Ctrl-C.

You’ll also need to create a Postman Collection, which is where you’ll add requests to send to your API.

Test #1: Can you create a new owner?

First, let’s confirm that we can create a new owner by sending a POST request to the /owners endpoint. Click on your new collection (if you haven’t yet created one, follow these steps to do so), and then click Add Request. Choose “POST” from the dropdown on the left, and enter localhost:3000/owners as the request URL.

Then, navigate to the Body tab, choose raw, and select JSON from the dropdown on the right. Add the following code to the request body:

{
    "name": "Jill Smith",
    "email": "jillsmith@example.com"
}

Now, click Send to confirm that the create action on the OwnersController is able to handle this request. If so, the API will return a status “201 Created,” and you will see the newly created owner data in the response body:

how to build an API

Take note of this owner’s ID, as you’ll need it in the next step.

Test #2: Can you create a new pet that is associated with an owner?

Now, let’s see whether you can create a new pet and link it to the owner we just created. To do so, add a new request to the collection, select “POST” from the dropdown, and enter localhost:3000/pets as the request URL. Then, just as you did when creating an owner, navigate to the Body tab, choose raw, and select JSON from the dropdown.

Because you want to associate this new pet with the owner you just created, you need to use the owner’s ID from the previous response as the value for the owner_id field in this new request. Add the following code to the request body, but be sure to enter your own owner_id if it is not 1.

{
        "name": "Nellie",
        "species": "Dog",
        "breed": "Jack Russell Terrier",
        "age": 4,
        "owner_id": 1
}

Now, click Send to confirm that your endpoint is working properly. As you can see in the below screenshot, the API returned a status “201 Created,” and we can see the newly created pet data in the response body:

how to build an API

Test #3: Can an owner have multiple pets?

We implemented a “one-to-many” relationship between the Owner and Pet models, so it’s important to verify that our API allows an owner to have many pets. To do so, let’s create another pet using the same POST request we just tested, remembering to use the same ID for the owner_id field:

{
        "name": "Fritz",
        "species": "Dog",
        "breed": "German Shorthaired Pointer",
        "age": 7,
        "owner_id": 1
}

Once again, click Send. If that request works as expected, it’s time to add a new request to your collection. This time, select “GET” from the dropdown and enter localhost:3000/owners/:id/pets as the request URL. Instead of sending JSON data in the request body, we’ll simply specify the owner_id as a path variable and click Send. If our data modeling is working correctly, you’ll see two pet objects in the response body:

how to build an API

These are just a few ways to manually test our pet tracker API. You’ll also want to check that the PATCH and DELETE methods are working as expected on both resources—and you may even try passing malformed data to the endpoints to ensure it is handled appropriately. Once you’ve confirmed that your endpoints are working correctly, you can save responses as examples to start building up a documentation collection. You can also leverage Postman’s built-in collaboration features to involve other stakeholders, such as API consumers, in the testing process.

Start building APIs today

Here, we showed you how to design, build, and test a simple REST API, but this tutorial was far from comprehensive. There’s much more you can do with both Rails and Postman, so check out the documentation to learn more. You can also enroll in the Postman Student Expert Program to improve your API literacy and development skills—even if you aren’t currently in school.

What do you think about this topic? Tell us in a comment below.

Comment

Your email address will not be published. Required fields are marked *


This site uses Akismet to reduce spam. Learn how your comment data is processed.