Random image generation API with Elixir and Phoenix
Table of contents

This tutorial walks you through building a simple Elixir + Phoenix API that generates random PNG images using Erlang’s EGD library. You’ll set up your environment with asdf
, add dependencies, implement an image service, create a controller and endpoint, and serve images directly from your API. By the end, you’ll have a working foundation you can extend for dynamic charts, placeholders, or PDF assets.
Until recently, I wasn’t aware of any tooling for generating images in Elixir, but while building internal tools that required millions of image attachments for PDFs, I had to figure it out. I learned a lot from that project, and decided to write a tutorial covering how to build an API for generating random images using Elixir and Phoenix.
Document Engine helps automate creation, delivery, and management of large volumes of assets.
Setup
For this tutorial, use the asdf
version manager to install the required versions of Elixir and Erlang.
- Follow the instructions here(opens in a new tab) to install and configure
asdf
. - Follow the instructions here(opens in a new tab) to install the
asdf
elixir
plugin. - Follow the instructions here(opens in a new tab) to install the
asdf
erlang
plugin.
Next, run these commands to create and navigate to a new directory:
$ mkdir image_generator$ cd image_generator
Then, create a file named .tool-versions
in the image_generator
, and add the following to the file:
elixir 1.18.4-otp-26erlang 26.2.2
From within the image_generator
directory in the terminal, run asdf install
. This will set up the required Elixir and Erlang versions on your machine.
Building the API
Run these commands to set up the Phoenix project:
$ mix archive.install hex phx_new$ mix phx.new image_generator_api --no-html --no-assets --no-ecto # Agree to install dependencies$ cd image_generator_api$ mix deps.get
Modify the mix.exs
file in the new image_generator_api
project to include the egd
dependency:
defmodule ImageGeneratorApi.MixProject do
#... Omitted for brevity.
# Type `mix help deps` for examples and options. defp deps do [
#...
{:bandit, "~> 1.5"}, {:egd, github: "erlang/egd"} ] endend
Run mix deps.get
again to install the new dependency.
Create a new image_generator_api/lib/image_generator_api/image_service.ex
file and add the following content:
defmodule ImageGeneratorApi.ImageService do @moduledoc """ Core image generation service using Erlang/EGD library """
def get_random_image() do length = Enum.random(150..300) {point1, point2, color} = random_image_specs(length) image = :egd.create(length, length) color = :egd.color(color) shape = Enum.random([:filledRectangle, :filledEllipse]) Kernel.apply(:egd, shape, [image, point1, point2, color])
:egd.render(image) end
def random_image_specs(points_upper_limit \\ 140) do # Generate 2D points 1 and 2. p1_x = Enum.random(50..points_upper_limit) p1_y = Enum.random(50..points_upper_limit) p2_x = Enum.random(50..points_upper_limit) p2_y = Enum.random(50..points_upper_limit)
# Generate random color. color = {Enum.random(0..256), Enum.random(0..256), Enum.random(0..256)}
{ {p1_x, p1_y}, {p2_x, p2_y}, color } endend
Then create a new image_generator_api/lib/image_generator_api_web/controllers/image_controller.ex
file and add the following content:
defmodule ImageGeneratorApiWeb.ImageController do use ImageGeneratorApiWeb, :controller
alias ImageGeneratorApi.ImageService
require Logger
@doc """ Generates a random image """ def image(conn, _params) do image_data = ImageService.get_random_image() send_image_response(conn, image_data) rescue e -> Logger.error("Image generation failed: #{inspect(e)}") send_error_response(conn, "An error occurred while generating the image") end
defp send_image_response(conn, image_data) do conn |> put_resp_content_type("image/png") |> put_resp_header("cache-control", "no-cache, no-store, must-revalidate") |> put_resp_header("pragma", "no-cache") |> put_resp_header("expires", "0") |> send_resp(200, image_data) end
defp send_error_response(conn, reason) do conn |> put_status(:internal_server_error) |> json(%{ error: "Image generation failed", reason: reason, timestamp: DateTime.utc_now() }) endend
Next, modify the router at image_generator_api/lib/image_generator_api_web/router.ex
by including a new GET /api/image
, like so:
defmodule ImageGeneratorApiWeb.Router do use ImageGeneratorApiWeb, :router
pipeline :api do plug :accepts, ["json"] end
scope "/api", ImageGeneratorApiWeb do pipe_through :api
get "/image", ImageController, :image end
#...
end
Finally, start the Phoenix server locally with mix phx.server
. After starting the server, in your browser, navigate to http://localhost:4000/api/image
to see a new image. Reloading the page should generate a new image every time.
Document Engine makes it easy to automate large-scale generation and delivery.
Conclusion
This project showed how to build a simple API for random image generation using Elixir, Phoenix, and Erlang’s EGD library(opens in a new tab). While the example was straightforward, the same approach can scale to more advanced use cases — such as generating charts, placeholders, or dynamic assets for PDFs and web applications.
If you’re new to Elixir, this is also a great way to get hands-on experience with Phoenix and explore how Elixir can power APIs beyond the typical web or data processing workloads.
For developers looking to take image generation further, Document Engine can help you automate the creation and delivery of large-scale image and PDF assets. Whether you’re generating millions of attachments for reports, invoices, or dynamic marketing materials, Document Engine streamlines the workflow so you can focus on building your app, not managing files.
Want to experiment more? Try extending this project by adding parameters for image size, colors, or shapes, or integrating it into a larger Elixir system — and see how Document Engine can help scale your output seamlessly.