Elixir Functions

 

Building RESTful APIs with Elixir and Plug

In the world of web development, creating robust and efficient APIs is a fundamental skill. RESTful APIs have become the de facto standard for building these interfaces due to their simplicity and scalability. Elixir, a functional programming language known for its concurrency and fault-tolerance capabilities, offers an exceptional environment for crafting RESTful APIs. In this article, we’ll dive into the Elixir landscape and explore how to leverage the Plug library to build RESTful APIs that are both powerful and maintainable.

Building RESTful APIs with Elixir and Plug

1. Why Elixir for RESTful APIs?

Elixir’s unique blend of functional programming and fault tolerance makes it an excellent choice for building APIs. Its lightweight processes, also known as actors, enable high concurrency while maintaining system stability. In addition, the Elixir ecosystem boasts libraries that simplify API development, with Plug being a standout choice.

2. Understanding Plug: The Backbone of Elixir Web Applications

At the heart of most Elixir web applications, including APIs, is the Plug library. Plug is a specification and set of conventions for composable web modules in Elixir. It provides a foundation for building HTTP middleware and request/response handling, making it an ideal choice for crafting RESTful APIs.

3. Setting Up a New Elixir Project

To get started, ensure you have Elixir installed on your system. You can create a new Elixir project using Mix, the Elixir build tool.

shell
mix new MyApiProject --module MyApiProject
cd MyApiProject

4. Adding Plug to the Project

Add Plug as a dependency in your project’s mix.exs file:

elixir
defp deps do
  [
    {:plug, "~> 1.11"}
  ]
end

After adding the dependency, fetch and compile the new dependency:

shell
mix deps.get

5. Creating a Basic Plug Pipeline

Plug introduces the concept of pipelines, a series of plugs that process incoming requests and outgoing responses. Let’s create a basic pipeline that logs incoming requests:

elixir
defmodule MyApiProject.MyPlugPipeline do
  import Plug.Conn

  def init(opts), do: opts

  def call(conn, _opts) do
    conn
    |> put_resp_header("x-api-version", "1.0")
    |> IO.inspect(label: "Incoming Request")
  end
end

In this code, we define a module MyApiProject.MyPlugPipeline that uses the Plug.Conn module. The call/2 function is the entry point for our plug and is where we manipulate the connection. We add a response header and then use IO.inspect/2 to log the incoming request.

6. Creating a Basic Router

Now that we have a basic pipeline, let’s create a router to route incoming requests to appropriate plugs. Create a module named MyApiProject.Router:

elixir
defmodule MyApiProject.Router do
  use Plug.Router

  plug MyApiProject.MyPlugPipeline

  # Define routes here
end

7. Defining Routes and Handling Requests

Let’s define some routes and use plugs to handle requests. For example, let’s create a route to handle a GET request to “/api/users”:

elixir
defmodule MyApiProject.Router do
  use Plug.Router

  plug MyApiProject.MyPlugPipeline

  get "/api/users", do: send_resp(conn, 200, "List of users")
end

In this code, when a GET request is made to “/api/users”, the send_resp/3 function sends a 200 OK response with the message “List of users”.

8. Parameter Handling and Dynamic Routes

RESTful APIs often require handling dynamic parameters in URLs. Let’s create a dynamic route that handles GET requests for a specific user ID:

elixir
defmodule MyApiProject.Router do
  use Plug.Router

  plug MyApiProject.MyPlugPipeline

  get "/api/users", do: send_resp(conn, 200, "List of users")
  get "/api/users/:id", do: show_user(conn)
  
  defp show_user(conn) do
    id = conn.params["id"]
    send_resp(conn, 200, "Showing user with ID #{id}")
  end
end

In this example, the “:id” parameter is captured and accessed using conn.params[“id”].

9. Creating a JSON API Response

Most APIs respond with JSON data. To handle this, we can create a plug that serializes data into JSON format:

elixir
defmodule MyApiProject.JsonResponsePlug do
  import Plug.Conn

  def init(opts), do: opts

  def call(conn, _opts) do
    conn
    |> put_resp_header("content-type", "application/json")
    |> send_resp(200, Jason.encode!(%{message: conn.resp_body}))
  end
end

Add this plug to the pipeline and use it in your router:

elixir
defmodule MyApiProject.Router do
  use Plug.Router

  plug MyApiProject.MyPlugPipeline
  plug MyApiProject.JsonResponsePlug

  # Define routes here
end

10. Error Handling

Error handling is a crucial aspect of API development. Let’s create a plug that handles errors and sends appropriate responses:

elixir
defmodule MyApiProject.ErrorHandlerPlug do
  import Plug.Conn

  def init(opts), do: opts

  def call(conn, _opts) do
    try do
      conn
    rescue
      _exception ->
        send_resp(conn, 500, "Internal Server Error")
    end
  end
end

Use the error handler plug in your router:

elixir
defmodule MyApiProject.Router do
  use Plug.Router

  plug MyApiProject.MyPlugPipeline
  plug MyApiProject.JsonResponsePlug
  plug MyApiProject.ErrorHandlerPlug

  # Define routes here
end

Conclusion

Building RESTful APIs with Elixir and Plug opens up a world of possibilities for creating performant and maintainable interfaces. Elixir’s functional programming paradigm and concurrency model align seamlessly with the demands of modern APIs, while the Plug library provides an elegant way to handle HTTP requests and responses. Armed with this knowledge, you’re well-equipped to embark on your journey of crafting powerful, efficient, and resilient RESTful APIs using the Elixir programming language.

Previously at
Flag Argentina
Brazil
time icon
GMT-3
Tech Lead in Elixir with 3 years' experience. Passionate about Elixir/Phoenix and React Native. Full Stack Engineer, Event Organizer, Systems Analyst, Mobile Developer.