Current section
Files
Jump to
Current section
Files
docs/phoenix-getting-started.md
# Getting started with Phoenix
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
```bash
mix phx.new gallery --no-ecto --install
cd gallery
```
Add ImagePipe to the dependencies in `mix.exs`:
```elixir
# mix.exs
defp deps do
[
{:image_pipe, "~> 0.4.0"},
# ...the generated dependencies
]
end
```
> #### ImagePipe needs `req` 0.8.0-rc.0 {: .warning}
>
> 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`:
>
> ```bash
> 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`:
```bash
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:
```elixir
# lib/gallery/application.ex
children = [
# ...
{ImagePipe,
name: Gallery.Images,
allow_unsigned: true,
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.File` reads files from a directory.
- `match: :path` sends every image path in a URL to this source.
- `root` is that directory, `priv/originals` in our app.
- `root_id` is a stable name for this directory.
`allow_unsigned: true` lets the instance serve image URLs that our app hasn't
signed, which is fine while it runs on our own machine. The end of this guide
says what to change before deploying.
## Mounting the Plug
`ImagePipe.Plug` answers image requests for the instance. Add a `forward` to
`lib/gallery_web/router.ex`, outside the `:browser` scope:
```elixir
# lib/gallery_web/router.ex
forward "/media", ImagePipe.Plug, instance: Gallery.Images
scope "/", GalleryWeb do
pipe_through :browser
get "/", PageController, :home
end
```
Image 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:
```bash
mix phx.server
```
```text
[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>
```text
GET /media/w=400/src/cat.jpg (in Chrome)
200 OK, content-type: image/avif, 400×267
```
Notice that:
- `/media` is where we mounted the Plug, and `src/cat.jpg` is
`priv/originals/cat.jpg`.
- The original is a JPEG, but Chrome got AVIF. Without a `format` option,
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:
```text
invalid transformation options
/w=400/cat.jpg
^
|
missing src/, src64/, or enc/ before the image path
```
## Showing 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:
```heex
<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`:
```elixir
# 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`:
```elixir
# lib/gallery_web.ex
defp html_helpers do
quote do
# ... the generated imports and aliases
use ImagePipe.URL.Helpers, instance: Gallery.Images
end
end
```
Stop 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:
```heex
<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:
```text
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/1
```
Notice 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:
```text
[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](requesting-images.md): how URLs are built, option
values, presets, and error responses.
- [Processing options](processing.md): every option, grouped by category.
- [Serving images from local files](serving-local-files.md),
[an HTTP origin](serving-from-http.md), or [S3](serving-from-s3.md):
where originals come from in production.
- `ImagePipe.Plug`: every option of the `forward`, and using it in a
`Plug.Router`.
- [Serving and processing in one app](combined-usage.md): build URLs and process
images in code with the same configuration.
- `ImagePipe.URL.Helpers`: what `image_url` checks, and failing the build or
tests on a mistake.
Before deploying, set up [URL signing](signing-urls.md) and remove
`allow_unsigned`, so only your app can create image URLs. Set up
[caching](caching-processed-images.md) so each copy is processed once.