Validate request params in Phoenix
source link: https://dev.to/bluzky/validate-request-params-in-phoenix-52a7
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Validate request params in Phoenix
Aug 14
・2 min read
In web developments, server receives lots of request data from client side. And when working with request params from client, my first rule is:
Don't believe the client
Imagine that you provide API to list all post using the filter from client, and user may add user_id
which point to other user, and you don't remove that unexpected field from request params. If you don't handle your logic carefully, you may accidentally leak data.
So every request should be cleaned from unexpected params, casted to the proper data type, and validated before passing to business layer.
You can achieve this by:
Using Ecto
If you are building a web server using Phoenix, I guess Ecto
is already in your dependencies. Just use it.
Thank to Ecto schemaless, you can build changeset from a dynamic schema:
defmodule MyApp.PostController do
...
defp index_params(params) do
default = %{
status: nil,
q: nil,
is_published: true
}
types = %{
status: :string,
q: :string,
is_published: :boolean
}
changeset =
{default, types}
|> Ecto.Changeset.cast(params, Map.keys(types))
if changeset.valid? do
{:ok, Ecto.Changeset.apply_changes(changeset)}
else
{:error, changeset}
end
end
def index(conn, params) do
with {:ok, valid_params} <- index_params(params) do
# do your logic
end
end
...
end
With Ecto you can do validation on your params as you do with your schema changeset.
This way is simple and most of you are familiar with it. But you have to write much code and cannot cast and validate nested params.
Use library Tarams
This library provide a simple way to define schema. Let's rewrite example above using tarams
.
First add this to your dependency list:
{:tarams, "~> 1.0.0"}
defmodule MyApp.PostController do
...
@index_params %{
status: :string,
q: :string
is_published: [type: :boolean, default: true],
page: [type: :integer, number: [min: 1]],
size: [type: :integer, number: [min: 10, max: 100]]
}
def index(conn, params) do
with {:ok, valid_params} <- Tarams.cast(params, @index_params) do
# do your logic
end
end
...
end
And it support nested params too
defmodule MyApp.PostController do
...
@create_params %{
title: [type: :string, required: true],
content: [type: :string, required: true],
tags: [type: {:array, :string}],
published_at: :naive_datetime,
meta: %{
tile: :string,
description: :string,
image: :string
}
}
def create(conn, params) do
with {:ok, valid_params} <- Tarams.cast(params, @create_params) do
MyApp.Content.create_post(valid_params)
end
end
...
end
Conclusion
All request params should be casted and validated at controller. Then you only work with data that you know what it is, and you don't have to worry about unexpected parameters.
Thanks for reading, hope it can helps.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK