VOOZH about

URL: https://blog.logrocket.com/build-rest-api-elixir-phoenix/

⇱ Building a REST API with Elixir and Phoenix - LogRocket Blog


2022-10-21
2334
#phoenix
Ganesh Mani
136257
πŸ‘ Image

See how LogRocket's Galileo AI surfaces the most severe issues for you

No signup required

Check it out

Phoenix is the most loved framework by developers in 2022, and by some margin too. It’s great for web development as part of the Elixir ecosystem and its reliability and scalability make it a good choice for any project.

πŸ‘ Build Rest API Phoenix Elixir

In this tutorial, we will explore what Elixir is, the Phoenix web framework, and how to build a REST API with Elixir and Phoenix for your projects.

Skip ahead

πŸš€ Sign up for The Replay newsletter

The Replay is a weekly newsletter for dev and engineering leaders.

Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.

What is Elixir and the Phoenix web framework?

Elixir is a functional, dynamically-typed language that is built on top of Erlang. Jose Valim, who developed Elixir, worked on the Ruby on Rails team and created it when he was attempting to solve performance bottlenecks with Ruby on Rails and Erlang.

For this reason, the syntax of Elixir has many similarities with Ruby on Rails syntax.

Elixir is mainly used to create highly scalable applications that are fault-tolerant and easily maintainable. Some of the key features of Elixir are:

  1. It compiles the code into byte code that runs on Erlang VM
  2. It emphasizes higher order functions and recursions
  3. Powerful pattern matching
  4. It is a dynamically typed language, so it checks all the types at runtime rather than compile time

Benefits of Elixir

Since Elixir is built on top of BEAM, an Erlang VM, it has some key characteristics that help to build great apps. These characteristics are:

  1. Concurrency: Elixir uses process threads for execution. These threads are isolated, CPU-based, and communicate through messages
  2. Scalability: Scaling an Elixir application is simple since it uses lightweight threads that can run with few processes
  3. Reliability: Building fault-tolerant applications is one of the main features of Elixir. When an Elixir application fails in production, the supervisor system restarts the lightweight process quickly, which reduces downtime

When it comes to building web applications, it is difficult to build basic functionalities from scratch every time, which is where the Phoenix web framework comes into the picture.

Introduction to Phoenix

Phoenix is a web framework in the Elixir ecosystem. It comes with modules out-the-box that help build highly scalable and fault-tolerant applications. Phoenix is a Model-View-Controller (MVC) framework similar to Ruby on Rails and Django.

One of the killer features of Phoenix framework is LiveView. Phoenix LiveView is a library that is built on top of Phoenix which helps in building real-time applications without writing client-side JavaScript. It calculates the page changes and push updates through WebSocket.

How Phoenix works

Plugs are the basic element of Phoenix. Plugs are a specification for composing a web application with functions β€” Phoenix receives an incoming request and converts it into Conn, which is a data structure that handles requests and responses in HTTP connections.

πŸ‘ Phoenix Plugs Web App Functions

Conn data structure is passed through several plugs to complete the functionality and return a response. To simplify it:

  1. Receives a request
  2. Converts it to conn
  3. Passes through several plugs
  4. Returns response

Lifecycle of Phoenix requests

As we’ve noted, an incoming request in phoenix goes through several plugs to return the required response β€” let’s look into the details of the plugs and process:

πŸ‘ Incoming Request To Endpoints

Phoenix receives a request at the endpoint and the endpoint converts it into a Conn data structure, forwarding it to the router.

The router pipelines the Conn data structure into the controller, and the controller interacts with the model to fetch data from the database and render it using templates. Templates can be HTML or JSON files. Here, the endpoint, router, and controllers are plugs β€” everything in Phoenix is a composable function that transforms data into different structure.

Creating a REST API using Elixir and Phoenix

Now we are familiar with the Phoenix web framework, let’s build a REST API with Elixir and Phoenix to use for a project. Here, we are going to create a REST API which delivers users information to a Postgres database.

The API will serve user information under /api/users, with GET, PUT, POST, DELETE requests.

To do this, we need to create database schema with required fields that are stored in the database:

  1. id β†’ Primary key in the table.
  2. name β†’ String contains user name.
  3. email β†’ Email field, it should be unique.
  4. role β†’ User role.
  5. address β†’ User address.

Prerequisites

Before you proceed further, I’d recommend you meet the following prerequisites:

  1. Basic understanding of the Elixir syntax. You can refer to the official docs to get started with the syntax
  2. Install Elixir in your machine. We will discuss how to install Elixir in the upcoming section
  3. Have Postgres installed in your machine. Alternatively, you can run Postgres using Docker (which is what we will be doing in this tutorial)
  4. Having the Postman client or an alternative to test APIs

Getting started

First and foremost, let’s install Elixir on your machine. To install elixir on macOS, you can use Homebrew package manager.


Over 200k developers use LogRocket to create better digital experiences

πŸ‘ Image
Learn more β†’

Installing Elixir on macOS

Before installation, update Homebrew using:

$ brew update

After this, you can install Elixir using the following:

$ brew install elixir

You also need to install the Elixir package manager Hex. Run the following command to install Hex:

$ mix local.hex

To verify that the installation is successful, you can run this to check the version:

$ elixir -v

πŸ‘ Verify Elixir Installation Successful

(Note: For other operating systems, you can refer the Elixir official guide that provides a simple step-by-step guide for installation)

Installing Phoenix

Now we’ve installed Elixir, Let’s bootstrap a project using the Phoenix framework to build a REST API.

$ mix archive.install hex phx_new 1.5.3

This installs Phoenix 1.5.3 on your machine. To create a new project, you need to run the following command in the terminal:

$ mix phx.new users_api --no-html --no-webpack --binary-id 

$ cd users_api

This will create a users_api directory, with all the boilerplate for a Phoenix application. It follows a directory structure like this:

β”œβ”€β”€ _build
β”œβ”€β”€ assets
β”œβ”€β”€ config
β”œβ”€β”€ deps
β”œβ”€β”€ lib
β”‚ β”œβ”€β”€ hello
β”‚ β”œβ”€β”€ hello.ex
β”‚ β”œβ”€β”€ hello_web
β”‚ └── hello_web.ex
β”œβ”€β”€ priv
└── test

(Note: The official Phoenix docs explain the directory structure in detail, and you can refer to it here)

The --no-html and  --no-webpack parameters instruct the command to not generate HTML files and static assets since we’re only building a REST API.

--binary-id will configure Ecto to use a UUID for database schemas such as primary key values.

Now, we will scaffold the application with boilerplate code. Let’s run the Postgres database and connect the application to the database.

Set up and configure database

Firstly, make sure you install Docker on your machine. Running Postgres via Docker is simple; just need run the following command:

$ docker run --name phoenix-postgres -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -p 5500:5432 -d postgres

Here, we specify --name for the Docker container and environment for POSTGRES_USER and POSTGRES_PASSWORD. We also need to map the port by specifying -p . Finally, we pull the Docker image in detached mode.


More great articles from LogRocket:


Once Postgres is up and running, we configure the database in the Phoenix application. Phoenix provides a config directory to setup databases, cron jobs, loggers, and stack traces in the application.

To configure the database in the development environment, change config/dev.exs with Postgres database credentials.

# Configure your database
config :users_api, UsersApi.Repo,
 username: "postgres",
 password: "postgres",
 database: "users_api_dev",
 hostname: "localhost",
 port: "5500",
 show_sensitive_data_on_connection_error: true,
 pool_size: 10

After this, you can create a database for the development environment like so:

$ mix ecto.create

And you can drop the database using this:

$ mix ecto.drop

Schema and migration

Once you create a database and configure it in the application, you need to model the data to access it inside the app.

Phoenix contexts

Before we start with schema design, it’s important to know about Phoenix contexts. Contexts are modules that group the related functionalities together. When you design an application, Phoenix helps to group modules based on the context. Think of it like domains in Domain-Driven Design.

Context will group different modules together based on the functionalities.

To give an example, Elixir Logger.info/1 is made up of several modules, but we can access those modules in a single Logger module context.

To create context for modeling, you can use the Phoenix generator command in the application:

$ mix phx.gen.context Admin User users 
 name:string email:string:unique role:string address:string

Here, we have:

  • Admin, as a context’s module name
  • User, as the schema’s module name
  • Users, as the database table name

To define the users field and its type while generating the database table, you can refer to the schema field definition from the hex docs.

Once you run the command, it will create lib/admin/user.ex, which contains the schema definition.

defmodule UsersApi.Admin.User do
 use Ecto.Schema
 import Ecto.Changeset
 @primary_key {:id, :binary_id, autogenerate: true}
 @foreign_key_type :binary_id
 schema "users" do
 field :address, :string
 field :email, :string
 field :name, :string
 field :role, :string
 timestamps()
 end
 @doc false
 def changeset(user, attrs) do
 user
 |> cast(attrs, [:name, :email, :role, :address])
 |> validate_required([:name, :email, :role, :address])
 |> unique_constraint(:email)
 end
end

It also scaffolds the REST API for the users module in users_api/admin.ex.

defmodule UsersApi.Admin do
 @moduledoc """
 The Admin context.
 """
 import Ecto.Query, warn: false
 alias UsersApi.Repo
 alias UsersApi.Admin.User
 @doc """
 Returns the list of users.
 ## Examples
 iex> list_users()
 [%User{}, ...]
 """
 def list_users do
 Repo.all(User)
 end
 @doc """
 Gets a single user.
 Raises `Ecto.NoResultsError` if the User does not exist.
 ## Examples
 iex> get_user!(123)
 %User{}
 iex> get_user!(456)
 ** (Ecto.NoResultsError)
 """
 def get_user!(id), do: Repo.get!(User, id)
 @doc """
 Creates a user.
 ## Examples
 iex> create_user(%{field: value})
 {:ok, %User{}}
 iex> create_user(%{field: bad_value})
 {:error, %Ecto.Changeset{}}
 """
 def create_user(attrs \\ %{}) do
 %User{}
 |> User.changeset(attrs)
 |> Repo.insert()
 end
 @doc """
 Updates a user.
 ## Examples
 iex> update_user(user, %{field: new_value})
 {:ok, %User{}}
 iex> update_user(user, %{field: bad_value})
 {:error, %Ecto.Changeset{}}
 """
 def update_user(%User{} = user, attrs) do
 user
 |> User.changeset(attrs)
 |> Repo.update()
 end
 @doc """
 Deletes a user.
 ## Examples
 iex> delete_user(user)
 {:ok, %User{}}
 iex> delete_user(user)
 {:error, %Ecto.Changeset{}}
 """
 def delete_user(%User{} = user) do
 Repo.delete(user)
 end
 @doc """
 Returns an `%Ecto.Changeset{}` for tracking user changes.
 ## Examples
 iex> change_user(user)
 %Ecto.Changeset{data: %User{}}
 """
 def change_user(%User{} = user, attrs \\ %{}) do
 User.changeset(user, attrs)
 end
end

Running migration

To run the migration for the defined data schema, you need to run the following command:

$ mix ecto.migrate

Once you run the migration, it will create a table in the database with the defined fields, as in the schema.

Scaffolding Controller and View

Now, we have the database schema and model defined in the application, we need to wire it with Controller and View to return the response.

To generate Controller and View for a specific module, Phoenix provides a generator command that can generate boilerplate code:

$ mix phx.gen.json Admin User users 
 name:string email:string:unique role:string address:string --no-context --no-schema

The above command generates:

  • A CRUD Controller for the users at lib/users_api_web/controllers/users_controller.ex
  • View, to render users’ JSON at lib/users_api_web/view/users_view.ex

We also mention --no-context and --no-schema, since we already generated them while creating the database schema.

Phoenix also provides users_api_web/controllers/fallback_controller.ex to handle errors and fallbacks for any failures. We can edit them to handle different error codes. For example, you can edit fallback controllers to handle UnAuthorized errors.

defmodule MyFallbackController do
 use Phoenix.Controller

 def call(conn, {:error, :not_found}) do
 conn
 |> put_status(:not_found)
 |> put_view(MyErrorView)
 |> render(:"404")
 end

 def call(conn, {:error, :unauthorized}) do
 conn
 |> put_status(403)
 |> put_view(MyErrorView)
 |> render(:"403")
 end
end

By default, Phoenix configures the fallback controller in the main controller β€” you can change them in users_api_web/controllers/user_controller.ex.

 action_fallback UsersApiWeb.FallbackController

Once you create a controller and view, you need to add a route for the controller in lib/users_api_web/router.ex.

defmodule UsersApiWeb.Router do
 use UsersApiWeb, :router
 pipeline :api do
 plug :accepts, ["json"]
 end
 scope "/api", UsersApiWeb do
 pipe_through :api
 get "/users", UserController, :index
 put "/users", UserController, :edit
 post "/users", UserController, :create
 delete "/users", UserController, :delete
 end
 # Enables LiveDashboard only for development
 #
 # If you want to use the LiveDashboard in production, you should put
 # it behind authentication and allow only admins to access it.
 # If your application does not have an admins-only section yet,
 # you can use Plug.BasicAuth to set up some basic authentication
 # as long as you are also using SSL (which you should anyway).
 if Mix.env() in [:dev, :test] do
 import Phoenix.LiveDashboard.Router
 scope "/" do
 pipe_through [:fetch_session, :protect_from_forgery]
 live_dashboard "/dashboard", metrics: UsersApiWeb.Telemetry
 end
 end
end

As you can see, you can group different routes into a single endpoint. Here, we nest all the /users routes inside /api scope. Inside /api scope, we configure CRUD for the Users API.

There is another simplified version for configuring a CRUD request for a module. We can use resources from the Phoenix router module:

resources "/users", UserController

By configuring its resources like this; it simplifies all the requests in a single line.

 scope "/api", UsersApiWeb do
 pipe_through :api
 resources "/users", UserController, except: [:new, :edit]

Now, you can run the application using the following:

$ mix phx.server

πŸ‘ Run The Application Compiling

Conclusion

And you’re done! Thanks for following along with this tutorial, I hope you found it useful and feel free to post your findings in the comments below. You can test the API at http://localhost:4000, and you can refer the complete code for this tutorial here.

Get set up with LogRocket's modern error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to get an app ID
  2. Install LogRocket via npm or script tag. LogRocket.init() must be called client-side, not server-side

    $ npm i --save logrocket 
    
    // Code:
    
    import LogRocket from 'logrocket'; 
    LogRocket.init('app/id');
     
    // Add to your HTML:
    
    <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
    <script>window.LogRocket && window.LogRocket.init('app/id');</script>
     
  3. (Optional) Install plugins for deeper integrations with your stack:
    • Redux middleware
    • NgRx middleware
    • Vuex plugin
Get started now
πŸ‘ Image
πŸ‘ Image
πŸ‘ Image

Stop guessing about your digital experience with LogRocket

Get started for free

Recent posts:

Debug Next.js apps with AI agents and next-browser

Learn how next-browser gives AI agents runtime context for debugging Next.js apps, including React props, hydration, PPR, forms, and performance.

πŸ‘ Image
Emmanuel John
Jun 17, 2026 β‹… 9 min read

Stop hardcoding LLM SDKs: Dynamic LLM routing with OpenRouter and Next.js

Build dynamic LLM routing in Next.js with OpenRouter, TanStack AI, task classification, model fallbacks, and cost-aware routing.

πŸ‘ Image
Chizaram Ken
Jun 16, 2026 β‹… 13 min read

What is TSRX?: What JSX would look like if it were designed today

TSRX adds first-class control flow, conditional hooks, and scoped styles to React via a TypeScript compiler extension β€” no new framework required.

πŸ‘ Image
Ikeh Akinyemi
Jun 12, 2026 β‹… 6 min read

How to add authentication to a React Native app with Better Auth

Learn how to build a full React Native auth system using Better Auth and Expo β€” with email/password login, Google OAuth, session persistence, and protected routes.

πŸ‘ Image
Chinwike Maduabuchi
Jun 9, 2026 β‹… 13 min read
View all posts

Would you be interested in joining LogRocket's developer community?

Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.

Sign up now