In this guide we'll add ImagePipe to a new Phoenix app, serve resized copies of an image from it, and show one on a page, with its URL built in the template.
You need Elixir 1.18 or newer, the Phoenix installer (mix archive.install hex phx_new), and a JPEG image.
Creating the app
mix phx.new gallery --no-ecto --install
cd gallery
Add ImagePipe to the dependencies in mix.exs:
# mix.exs
defp deps do
[
{:image_pipe, "~> 0.1.0"},
# ...the generated dependencies
]
endImagePipe needs req 0.8.0-rc.0
ImagePipe depends on req ~> 0.8.0-rc.0, and a new Phoenix app locks an
older version. Unlock it before fetching, or mix deps.get stops with a
version conflict over req:
mix deps.unlock req
mix deps.get
Adding a folder of images
ImagePipe reads originals from a folder and makes processed copies of them on
request. Create priv/originals and copy your image into it as cat.jpg:
mkdir -p priv/originals
cp /path/to/your/image.jpg priv/originals/cat.jpg
The examples below use a 4000×2667 image. With your image, the widths match and the heights follow its proportions.
Starting ImagePipe
Next, we start ImagePipe in the app's supervision tree and tell it where the
originals are. Add it to the children in lib/gallery/application.ex, before
the endpoint:
# lib/gallery/application.ex
children = [
# ...
{ImagePipe,
name: Gallery.Images,
sources: [
originals: [
adapter: ImagePipe.Source.File,
match: :path,
options: [root: Application.app_dir(:gallery, "priv/originals"), root_id: "originals"]
]
]},
# Start to serve requests, typically the last entry
GalleryWeb.Endpoint
]This starts an ImagePipe instance named Gallery.Images with one source
named originals. A source is a place ImagePipe reads originals from:
adapter: ImagePipe.Source.Filereads files from a directory.match: :pathsends every image path in a URL to this source.rootis that directory,priv/originalsin our app.root_idis a stable name for this directory.
Mounting the Plug
ImagePipe.Plug answers image requests for the instance. Add a forward to
lib/gallery_web/router.ex, outside the :browser scope:
# lib/gallery_web/router.ex
forward "/media", ImagePipe.Plug, instance: Gallery.Images
scope "/", GalleryWeb do
pipe_through :browser
get "/", PageController, :home
endImage requests don't need the session, CSRF protection, or HTML layout of the
:browser pipeline. We use /media rather than /images because a new
Phoenix app already serves its static files under /images.
Resizing an image
Start the app:
mix phx.server
[info] Running GalleryWeb.Endpoint with Bandit 1.x.x at 127.0.0.1:4000 (http)An image URL lists processing options, then src/, then the image's path in
the source. Let's ask for cat.jpg at 400 pixels wide. Open this URL:
http://localhost:4000/media/w=400/src/cat.jpg
GET /media/w=400/src/cat.jpg (in Chrome)
200 OK, content-type: image/avif, 400×267Notice that:
/mediais where we mounted the Plug, andsrc/cat.jpgispriv/originals/cat.jpg.- The original is a JPEG, but Chrome got AVIF. Without a
formatoption, ImagePipe picks AVIF or WebP when the browser accepts it.
If you mistype the file name, ImagePipe answers 404 with the body
source not found. If you leave out src/, as in /media/w=400/cat.jpg,
it answers 400, and the body points at the problem:
invalid transformation options
/w=400/cat.jpg
^
|
missing src/, src64/, or enc/ before the image pathShowing the image on a page
Image URLs are ordinary paths in the app, so templates use them with ~p.
Replace the contents of
lib/gallery_web/controllers/page_html/home.html.heex with:
<Layouts.app flash={@flash}>
<img src={~p"/media/w=400/h=300/fit=cover/src/cat.jpg"} alt="A cat" />
</Layouts.app>Open http://localhost:4000. The page shows a 400×300 copy of the image,
cropped to fill the box by fit=cover.
Building URLs in templates
ImagePipe can build URLs from the instance's settings. First, set
base_url to the path of the forward:
# lib/gallery/application.ex
{ImagePipe,
name: Gallery.Images,
base_url: "/media",
sources: [
# ...
]},Then make ImagePipe's image_url helper available in every template. In
lib/gallery_web.ex, add a line to html_helpers:
# lib/gallery_web.ex
defp html_helpers do
quote do
# ... the generated imports and aliases
use ImagePipe.URL.Helpers, instance: Gallery.Images
end
endStop the server (Ctrl+C twice) and start it again with
mix phx.server, so the instance picks up base_url. Then replace
the hand-written path in the template:
<Layouts.app flash={@flash}>
<img
src={image_url("cat.jpg", group: [resize: [width: 400, height: 300, fit: :cover]])}
alt="A cat"
/>
</Layouts.app>Reload http://localhost:4000. The page looks the same, and the image's
address is /media/w=400/h=300/fit=cover/src/cat.jpg, the path we wrote by
hand before. Each group: holds the options of one processing step, and
resize: writes w=, h=, and fit=.
Now change fit: :cover to fit: :bogus and save. When Phoenix
recompiles the template, the terminal shows a warning that points at its
line:
warning: image_url: invalid_value at {:group, 0, :fit}: invalid value for :fit option: expected one of [:contain, :cover, :stretch, :auto], got: :bogus
└─ lib/gallery_web/controllers/page_html/home.html.heex:3: GalleryWeb.PageHTML.home/1Notice that the mistake shows up when the template compiles. The page still
renders: the server rejects the image's URL with 400, so the browser shows
a broken image in place of the cat, and each render logs:
[warning] image_url (lib/gallery_web/controllers/page_html/home.html.heex:3): invalid_value at {:group, 0, :fit}Change it back to fit: :cover.
Next steps
We started an ImagePipe instance, mounted it in the router, and served resized copies of an image to a page, with URLs written by hand and built in the template. From here:
- Requesting images: how URLs are built, option values, presets, and error responses.
- Processing options: every option, grouped by category.
- Serving images from local files, an HTTP origin, or S3: where originals come from in production.
ImagePipe.Plug: every option of theforward, and using it in aPlug.Router.- Serving and processing in one app: build URLs and process images in code with the same configuration.
ImagePipe.URL.Helpers: whatimage_urlchecks, and failing the build or tests on a mistake.
Before deploying, set up URL signing so only your app can create image URLs, and caching so each copy is processed once.