Random image generation API with Elixir and Phoenix

Table of contents

    Random image generation API with Elixir and Phoenix
    TL;DR

    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.

    Looking to scale your image or PDF generation?

    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.

    Next, run these commands to create and navigate to a new directory:

    Terminal window
    $ 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-26
    erlang 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:

    Terminal window
    $ 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"}
    ]
    end
    end

    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
    }
    end
    end

    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()
    })
    end
    end

    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.

    Want to embed images in PDFs or reports?

    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.

    Oghenerukevwe Henrietta Kofi

    Oghenerukevwe Henrietta Kofi

    Server and Services Engineer

    Rukky joined Nutrient as an intern in 2022 and is currently a software engineer on the Server and Services Team. She’s passionate about building great software, and in her spare time, she enjoys reading cheesy novels, watching films, and playing video games.

    Explore related topics

    FREE TRIAL Ready to get started?