# JavaScriptでバリデーションするときのAPI設計

**URL:** <https://discourse.clean-rails.org/t/javascript-api/88>\
**Category:** Uncategorized\
**Created:** [2018 年 4 月 17 日午後 4:56 UTC](https://discourse.clean-rails.org/t/javascript-api/88 "2018-04-17T16:56:17Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![sinsoku](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/sinsoku/32/887_2.png) [@sinsoku](https://discourse.clean-rails.org/u/sinsoku)\
**Post date:** [2018 年 4 月 17 日午後 4:56 UTC](https://discourse.clean-rails.org/t/javascript-api/88/1 "2018-04-17T16:56:17Z")

</div>

フォームのバリデーションをJavaScriptでやりたい場合、どんなAPI設計にするのが良いか？

## 例

ユーザーのバリデーションが下記のように定義されているとする。

```ruby
class User < ApplicationRecord
  validates :user_name, presence: true, uniqueness: true, format: { with: /\w/ }
  validates :age, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
end

```

このバリデーションをどうにかJavaScript側からも使いたいけど、こういう場合はバリデーション用のAPIを毎回作る感じですかね？

```ruby
module Validations
  class UsersController < ::ApplicationController
    # POST /validations/user.json
    def create
      if @user.valid?
        render json: []
      else
        render json: @user.errors
      end
    end
  end
end

```

もしくは、モデル単位じゃなくて各フォームでAPIを分けておいた方が良かったりするかも？

```ruby
module Validations
  class SignupFormsController < ::ApplicationController
    # POST /validations/signup.json
    def create
      # do something
    end
  end
end

```

あとは通信を極力減らすため、あえてJavaScriptで実現可能なバリデーションはJavaScript側に冗長でも実装してしまい、DBのアクセスが必要なものだけAPIを用意する方法もある？

uniquenessだけはDBをチェックする必要があるので、それだけAPIにする例。

```ruby
class UserNamesController < ApplicationController
  # GET /user_names/:user_name
  def show
    if User.where(user_name: params[:user_name).exists?
      head :ok
    else
      head :not_found
    end
  end
end

```

本題から逸れるけど「 **200 OK** は存在するユーザー名なので登録できないようにエラーメッセージを表示する」のは分かり辛いと思いつつ、RESTful に作るとAPIはこうなるのでは…という複雑な気持ち。

---

<div class="post-metadata">

**Author:** ![shishi](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/shishi/32/901_2.png) [@shishi](https://discourse.clean-rails.org/u/shishi)\
**Post date:** [2018 年 4 月 19 日午後 12:03 UTC](https://discourse.clean-rails.org/t/javascript-api/88/2 "2018-04-19T12:03:41Z")

</div>

以前同じような悩みを抱えたことがあります。

その際は結局HTML5で実現可能な範囲のバリデーションを書き、他はJavaScriptというかフロントエンド側で処理することは諦めました。API設計に対する回答としては自分でもどうかと思いますが妥協案として…

---

<div class="post-metadata">

**Author:** ![hshimoyama](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/hshimoyama/32/893_2.png) [@hshimoyama](https://discourse.clean-rails.org/u/hshimoyama)\
**Post date:** [2018 年 4 月 20 日午前 2:22 UTC](https://discourse.clean-rails.org/t/javascript-api/88/3 "2018-04-20T02:22:10Z")

</div>

どこまでやるかにも依るのですが、transaction resource を使うという方法はどうでしょうか。

`POST /user_transactions`, `PATCH /user_transactions/:id` でこういう response を返すイメージです。

```json
{
  "id": 12345,
  "errors": [
    {
      "type": "https://example.net/validation-error",
      "title": "バリデーションエラーが発生しました",
      "invalid-params": [
        {
          "name": "user[name]",
          "reason": "このユーザー名は既に使用されています"
        }
      ]
   }
}

```

(あるいはちゃんと RFC7807 に沿って POST 時に `application/problem+json` を返してあげても良さそう)  
transaction はまた別の難しさが発生したり、場合によってはオーバースペックかと思いますが、バリデーションだけに着目するとフロント側でサーバを慮ったバリデーションロジックを書かなくて済む、というメリットがありそうです。

---

<div class="post-metadata">

**Author:** ![willnet](https://discourse.clean-rails.org/user_avatar/discourse.clean-rails.org/willnet/32/4_2.png) [@willnet](https://discourse.clean-rails.org/u/willnet)\
**Post date:** [2018 年 8 月 26 日午前 8:12 UTC](https://discourse.clean-rails.org/t/javascript-api/88/4 "2018-08-26T08:12:34Z")

</div>

プロジェクトの要件によって変わってきそうですが、基本的には

> [@sinsoku](#):
>
> 通信を極力減らすため、あえてJavaScriptで実現可能なバリデーションはJavaScript側に冗長でも実装してしまい、DBのアクセスが必要なものだけAPIを用意する方法

が良いのかな、と思っています。

※サーバ負荷に余裕があって手抜きが必要(クライアントサイド側を作り込む余力がない)みたいな場合を除く

> [@sinsoku](#):
>
> 本題から逸れるけど「 **200 OK** は存在するユーザー名なので登録できないようにエラーメッセージを表示する」のは分かり辛いと思いつつ、RESTful に作るとAPIはこうなるのでは…という複雑な気持ち。

HTTPのステータスコードと表示を合わせたいのであれば、こういう感じでもいけませんかね

```auto
class UserNamesValidationsController < ApplicationController
  # POST /user_names/:user_name/validation
  def create
    if User.where(user_name: params[:user_name).exists?
      head ::unprocessable_entity
    else
      head :ok
    end
  end
end

```
