TechieClues TechieClues
Updated date Apr 10, 2023
In this article, we explore how to build a REST API using Elixir and Phoenix, a powerful web framework known for its scalability and fault tolerance. We cover the basics of setting up a Phoenix project, defining a User schema, creating CRUD endpoints with a user controller, writing tests for the controller, and testing the API endpoints using curl commands.

Introduction:

Elixir, a dynamic and functional programming language, is gaining popularity for building scalable and fault-tolerant applications. One of the prominent web frameworks for Elixir is Phoenix, which provides an excellent foundation for building web applications and APIs.

In this article, we will walk through the process of building a REST API with Elixir and Phoenix, covering everything from setting up a new Phoenix project to writing API endpoints, handling requests, and generating responses.

We will also discuss various concepts such as routing, controllers, and views in Phoenix, along with error handling and authentication. So let's dive in and explore how to build a REST API with Elixir and Phoenix!

Prerequisites:

Before we begin, you should have some basic knowledge of Elixir and Phoenix.

If you are new to Elixir, you can check out the official Elixir documentation (https://elixir-lang.org/docs.html) and Phoenix documentation (https://hexdocs.pm/phoenix/overview.html) to get started. You will also need to have Elixir and Phoenix installed on your local machine. You can install Elixir by following the instructions on the Elixir website (https://elixir-lang.org/install.html) and Phoenix by following the instructions on the Phoenix website (https://hexdocs.pm/phoenix/installation.html). Once you have Elixir and Phoenix installed, you are ready to build your REST API!

Creating a New Phoenix Project:

To create a new Phoenix project, we can use the mix the command-line tool, which comes with Elixir. Open your terminal and run the following command:

mix phx.new my_api

This will create a new Phoenix project with the name "my_api" in a directory with the same name. You can replace "my_api" with the desired name for your project. This command will generate the basic structure of a Phoenix project, including configuration files, a supervision tree, and an initial endpoint.

Next, navigate to the project directory by running:

cd my_api

Now, let's start the Phoenix server by running:

mix phx.server

This will start the Phoenix server on the default port 4000, and you should be able to access the default Phoenix welcome page by opening your web browser and navigating to http://localhost:4000. This confirms that your Phoenix project is up and running!

Routing in Phoenix:

In Phoenix, routing is the process of mapping URLs to functions that handle requests. Phoenix uses a router to define routes, which are configured in the router.ex file located in the lib/my_api_web directory. Let's take a look at an example of how to define a route in Phoenix.

Open the router.ex file and replace the default code with the following:

defmodule MyApiWeb.Router do
  use MyApiWeb, :router

  # Define a route for the root URL
  get "/", PageController, :index

  # Define a route for a RESTful resource "users"
  resources "/users", UserController
end

In this example, we defined two routes. The first route maps the root URL ("/") to the index function in the PageController module. The second route defines a RESTful resource "users", which maps various HTTP methods (GET, POST, PUT, DELETE, etc.) to corresponding functions in the UserController module. This is a common pattern for defining routes in Phoenix, where URLs are mapped to controller functions using the HTTP method and the name of the resource.

Controllers in Phoenix:

In Phoenix, controllers handle requests and generate responses. Controllers are responsible for processing incoming requests, performing business logic, and returning appropriate responses. Let's create a controller for our REST API.

To create a new controller, we can use the mix command-line tool. In the terminal, run the following command:

mix phx.gen.html User users name:string age:integer

This command will generate a UserController module with CRUD (Create, Read, Update, Delete) actions for a "User" resource, along with HTML views for rendering the responses. Since we are building a REST API, we don't need the HTML views, so we can remove them to keep our code clean and focused on the API functionality.

Open the generated user_controller.ex file located in the lib/my_api_web/controllers directory, and remove the HTML views, leaving only the actions like this:

defmodule MyApiWeb.UserController do
  use MyApiWeb, :controller

  def index(conn, _params) do
    # Implement logic to fetch and return all users
  end

  def show(conn, %{"id" => id}) do
    # Implement logic to fetch and return a specific user by id
  end

  def create(conn, %{"user" => user_params}) do
    # Implement logic to create a new user with user_params
  end

  def update(conn, %{"id" => id, "user" => user_params}) do
    # Implement logic to update a specific user by id with user_params
  end

  def delete(conn, %{"id" => id}) do
    # Implement logic to delete a specific user by id
  end
end

In each action, we can implement the business logic to handle the corresponding CRUD operation for the "User" resource. For example, in the index action, we can fetch all users from the database and return them as a JSON response.

Generating JSON Responses:

In a REST API, responses are usually returned in JSON format. Phoenix provides built-in support for generating JSON responses using the render function and the :json view.

Let's update the UserController to generate JSON responses. Replace the code in the index action with the following:

def index(conn, _params) do
  users = Repo.all(User)
  render(conn, "index.json", users: users)
end

In this example, we fetch all users from the database using the Repo.all function, and then pass them to the render function along with the name of the JSON view ("index.json"). We can create this view by running the following command in the terminal:

mix phx.gen.json User users name:string age:integer

This command generates a UserView module with functions for rendering JSON responses. Open the generated user_view.ex file located in the lib/my_api_web/views directory, and update the index function to define how the users should be rendered as JSON:

defmodule MyApiWeb.UserView do
  use MyApiWeb, :view

  def render("index.json", %{users: users}) do
    %{data: users}
  end

  # Implement other functions for rendering JSON responses
end

In this example, we define a function render("index.json", %{users: users}) that takes a map with a key "users" as an argument, and returns a JSON response with a key "data" that contains the users. We can define similar functions for other actions to customize the JSON responses according to our needs.

Handling Requests and Parameters:

In Phoenix, requests from clients are represented as connection (conn) structures, which contain information about the HTTP method, URL path, query parameters, request body, headers, and more. We can use pattern matching to extract data from the conn structure and use it in our controller actions.

For example, in the show action of the UserController, we can extract the id parameter from the URL path and use it to fetch a specific user from the database. Here's an example implementation:

def show(conn, %{"id" => id}) do
  user = Repo.get(User, id)
  
  case user do
    nil ->
      render(conn, "error.json", message: "User not found", status: 404)
    _ ->
      render(conn, "show.json", user: user)
  end
end

In this example, we use pattern matching to extract the id parameter from the URL path, and then use the Repo.get function to fetch the user with the corresponding id from the database. We then use the render function to generate a JSON response with the user data, or an error message if the user is not found.

We can also handle request parameters in the request body, such as when creating or updating a resource. In the create and update actions of the UserController, we can extract the user_params from the request body and use them to create or update a user in the database. Here's an example implementation:

def create(conn, %{"user" => user_params}) do
  changeset = User.changeset(%User{}, user_params)
  
  case Repo.insert(changeset) do
    {:ok, user} ->
      render(conn, "show.json", user: user)
    {:error, changeset} ->
      render(conn, "error.json", message: "Failed to create user", errors: changeset.errors, status: 422)
  end
end

def update(conn, %{"id" => id, "user" => user_params}) do
  user = Repo.get(User, id)
  
  case User.update(user, user_params) do
    {:ok, user} ->
      render(conn, "show.json", user: user)
    {:error, changeset} ->
      render(conn, "error.json", message: "Failed to update user", errors: changeset.errors, status: 422)
  end
end

In these examples, we use the changeset concept in Ecto, which represents the changes we want to make to a resource. We use the changeset to validate and manipulate the user_params before inserting or updating the user in the database. If the changeset is valid, we use the Repo.insert or User.update functions to perform the corresponding CRUD operation, and then generate a JSON response with the updated user data or an error message if the changeset is invalid.

Testing the REST API:

As with any software development project, testing is an important aspect of building a REST API to ensure its correctness and reliability. Phoenix provides a built-in testing framework that allows us to write tests for our controller actions and views.

Let's write some tests for the UserController to ensure that our REST API is working correctly. Create a user_controller_test.exs file in the test/my_api_web/controllers directory, and write the following tests:

defmodule MyApiWeb.UserControllerTest do
  use MyApiWeb.ConnCase

  alias MyApiWeb.User

  test "GET /users returns all users as JSON", %{conn: conn} do
    user1 = insert(:user)
    user2 = insert(:user)

    conn = get(conn, "/users")

    assert json_response(conn, 200) == [
      %{id: user1.id, name: user1.name, age: user1.age},
      %{id: user2.id, name: user2.name, age: user2.age}
    ]
  end

  test "GET /users/:id returns a specific user as JSON", %{conn: conn} do
    user = insert(:user)

    conn = get(conn, "/users/#{user.id}")

    assert json_response(conn, 200) == %{id: user.id, name: user.name, age: user.age}
  end

  test "GET /users/:id returns a 404 error for non-existent user", %{conn: conn} do
    conn = get(conn, "/users/999")

    assert json_response(conn, 404) == %{error: "User not found"}
  end

  test "POST /users creates a new user and returns the user as JSON", %{conn: conn} do
    user_params = %{name: "John Doe", age: 30}

    conn = post(conn, "/users", %{user: user_params})

    assert json_response(conn, 201) == %{id: _, name: "John Doe", age: 30}
  end

  test "POST /users returns a 422 error for invalid user params", %{conn: conn} do
    user_params = %{name: "", age: -5}

    conn = post(conn, "/users", %{user: user_params})

    assert json_response(conn, 422) == %{error: "Failed to create user", errors: _}
  end

  test "PUT /users/:id updates an existing user and returns the user as JSON", %{conn: conn} do
    user = insert(:user)
    user_params = %{name: "Jane Smith", age: 35}

    conn = put(conn, "/users/#{user.id}", %{user: user_params})

    assert json_response(conn, 200) == %{id: user.id, name: "Jane Smith", age: 35}
  end

  test "PUT /users/:id returns a 404 error for non-existent user", %{conn: conn} do
    user_params = %{name: "Jane Smith", age: 35}

    conn = put(conn, "/users/999", %{user: user_params})

    assert json_response(conn, 404) == %{error: "User not found"}
  end

  test "PUT /users/:id returns a 422 error for invalid user params", %{conn: conn} do
    user = insert(:user)
    user_params = %{name: "", age: -5}

    conn = put(conn, "/users/#{user.id}", %{user: user_params})

    assert json_response(conn, 422) == %{error: "Failed to update user", errors: _}
  end

  test "DELETE /users/:id deletes an existing user and returns a 204 status", %{conn: conn} do
    user = insert(:user)

    conn = delete(conn, "/users/#{user.id}")

    assert conn.status == 204
    assert Repo.get(User, user.id) == nil
  end

  test "DELETE /users/:id returns a 404 error for non-existent user", %{conn: conn} do
    conn = delete(conn, "/users/999")

    assert json_response(conn, 404) == %{error: "User not found"}
  end
end

Now that we have our tests written, let's run them using mix test in the terminal to make sure everything is working as expected. If all tests pass, we can move on to running the application and testing the API endpoints with a tool like curl or a web-based API client like Postman.

Once the tests have passed, we can start the Phoenix server by running mix phx.server in the terminal. This will start the server at the default http://localhost:4000 URL.

Now let's test the API endpoints using curl in the terminal. Here are some example commands:

Get all users:

curl -X GET http://localhost:4000/users

Output:

[
  {
    "id": 1,
    "name": "John Doe",
    "age": 30
  },
  {
    "id": 2,
    "name": "Jane Smith",
    "age": 35
  }
]

Get a specific user:

curl -X GET http://localhost:4000/users/1

Output:

{
  "id": 1,
  "name": "John Doe",
  "age": 30
}

Create a new user:

curl -X POST -H "Content-Type: application/json" -d '{"user": {"name": "Alice", "age": 25}}' http://localhost:4000/users

Output:

{
  "id": 3,
  "name": "Alice",
  "age": 25
}

Update an existing user:

curl -X PUT -H "Content-Type: application/json" -d '{"user": {"name": "Bob", "age": 40}}' http://localhost:4000/users/1

Output:

{
  "id": 1,
  "name": "Bob",
  "age": 40
}

Delete a user:

curl -X DELETE http://localhost:4000/users/3

Output:

No output, just a 204 status indicating that the user has been successfully deleted.

Great! Our API is now functional and we have tested all the CRUD operations for the UserController endpoints using curl commands.

Conclusion:

In this article, we built a REST API using Elixir and Phoenix, a powerful web framework for building scalable applications. We covered the basics of setting up a Phoenix project, defining a User schema, creating a UserController with CRUD endpoints, writing tests for the controller, and testing the API endpoints using curl commands.

Elixir and Phoenix provide a robust and efficient way to build high-performance APIs, with features such as concurrency, fault tolerance, and scalability. With Phoenix's powerful router and controller system, it's easy to define RESTful endpoints and handle HTTP requests and responses in a clean and organized manner.

ABOUT THE AUTHOR

TechieClues
TechieClues

I specialize in creating and sharing insightful content encompassing various programming languages and technologies. My expertise extends to Python, PHP, Java, ... For more detailed information, please check out the user profile

https://www.techieclues.com/profile/techieclues

Comments (0)

There are no comments. Be the first to comment!!!