Phoenix

Rehber: Phoenix API uygulaması #1

5
murat

Merhabalar, uzun bir süredir yan projelerde kullanarak öğrenmeye çalıştığım Elixir dili için bir rehber niteliğinde notlar paylaşacağım yazı serime hoş geldiniz. Bu yazıya sıklıkla Ruby on Rails ile karşlılaştırılmaları yapılan Phoenix web framework ile API-only bir uygulama yazma rehberi olarak bakabilirsiniz. Hazırsam başlayayım... :)

Yeni bir Phoenix projesi oluşturma

Bu adımda Elixir, Erlang ve Phoenix framework'ün sisteminizde kurulu olduğunu varsayıyorum. Değilse eğer sizi şuraya yönlendirebilirim.

Aşağıdaki komutu terminalimizde çalıştırarak bir phoenix projesi açalım.

mix phx.new app --no-html --no-webpack

--no-html ve --no-webpack flagleri projemizin bir front-endi olmadığını belirtiyor, ve phx.new mix taskı da bunu anlayarak front-endsiz bir proje açıyor. Bu komut bulunduğunuz dizinde ./app adında bir dizin açarak içine phoenix boilerplate'i yerleştirecektir. Peşine mix deps.get taskını çalıştırarak bağımlılıkları indirecek ve dizinin içine girecektir.

Şimdi bir API uygulamasının olmazsa olmazı olan auth endpointlerinden başlayabiliriz.

Auth contexti ile User modeli

Öncelikle Auth contexti altında User data modelimizi oluşturalım.

mix phx.gen.context Auth User users name:string email:string:unique password_digest:string confirmed_at:datetime

Phoenix bu Auth contexti altında User data modelimizi oluştururken users adında bir tablo oluşturmak için de gerekli migration dosyalarımızı priv/repo/migrations dizini altına oluşturdu. password_digest isimli string olan alanımız bcrypt ile şifrelemek için mix.exs dosyasında deps fonksiyonuna aşağıdaki hex paketini ekleyelim.

# Add bcrypt for pass hashing in mix.exs
{:bcrypt_elixir, "~>  2.0"}

mix deps.get komutunu çalıştırınca bcrypt_elixir paketi hex.pm den indirilerek yüklenecektir.

Bu arada testlerde performansı arttırmak amacı ile config/test.exs konfigürasyon dosyasına aşağidaki ayarları ekleyerek bcrypt güvenlik seviyesini düşürelim.

# config/test.exs
config :bcrypt_elixir, :log_rounds, 4

lib/app/auth/user.ex dosyasını açıp password adında sanal bir field oluşturalım.

field :password, :string, virtual: true
field :password_digest, :string

virtual: true bu field'ın veritabanında olmadığını belirtmektedir. Bu field'ı kullanıcı girdisi alacağız ve üzerinde validasyonlarımızı yapıp, testlerimizden geçer ise password_digest verisini bcrypt ile üreteceğiz.

Bunun için changeset fonksiyonunu aşağıdaki gibi değiştirelim.

def changeset(user, attrs) do
  user
  |> cast(attrs, ~w(name email password confirmed_at)a)
  |> validate_required(~w(name email password)a)
  # E-mail validasyonu
  |> unique_constraint(:email)
  |> validate_format(:email, ~r/([\w-\.]+)@((?:[\w]+\.)+)([a-zA-Z]{2,4})/)
  # Password validasyonu (min 6 karakter ve confirmation ister)
  |> validate_length(:password, min: 6)
  |> validate_confirmation(:password)
  # Changeset'e password_digest ekle
  |> put_password_digest()
end

defp put_password_digest(
    %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
  ) do
  change(changeset, password_digest: Bcrypt.hash_pwd_salt(password))
end

defp put_password_digest(changeset) do
  changeset
end

Yukarıda yorum satırlarında bahsettiğim gibi validasyonlarımız gerçekleşecek ve sıkıntısız changeset geldi ise bcrypt ile şifrelenmiş bir password_digest değeri oluşacaktır.

Otomatik oluşturulan auth testleri bizim eklediklerimiz ile bozulduğu için orayı düzeltsek iyi olur. *_attrs değerlerine bakınız...

# test/app/auth_test.exs

defmodule App.AuthTest do
  use App.DataCase

  alias App.Auth

  describe "users" do
    alias App.Auth.User

    @valid_attrs %{
      confirmed_at: ~N[2010-04-17 14:00:00],
      email: "email@email.com",
      locked_at: ~N[2010-04-17 14:00:00],
      name: "name",
      password: "password",
      password_confirmation: "password"
    }
    @update_attrs %{
      confirmed_at: ~N[2011-05-18 15:01:01],
      email: "updated@email.com",
      locked_at: ~N[2011-05-18 15:01:01],
      name: "updated name",
      password: "updated.password",
      password_confirmation: "updated.password"
    }
    @invalid_attrs %{
      confirmed_at: nil,
      email: nil,
      locked_at: nil,
      name: nil,
      password: nil,
      password_confirmation: false
    }

    def user_fixture(attrs \\ %{}) do
      {:ok, user} =
        attrs
        |> Enum.into(@valid_attrs)
        |> Auth.create_user()

      user
    end

    test "list_users/0 returns all users" do
      user = user_fixture()
      assert Auth.list_users() == [%User{user | password: nil}]
    end

    test "get_user!/1 returns the user with given id" do
      user = user_fixture()
      assert Auth.get_user!(user.id) == %User{user | password: nil}
    end

    test "create_user/1 with valid data creates a user" do
      assert {:ok, %User{} = user} = Auth.create_user(@valid_attrs)
      assert user.confirmed_at == ~N[2010-04-17 14:00:00]
      assert user.email == "email@email.com"
      assert user.locked_at == ~N[2010-04-17 14:00:00]
      assert user.name == "name"
      assert Bcrypt.verify_pass("password", user.password_digest)
    end

    test "create_user/1 with invalid data returns error changeset" do
      assert {:error, %Ecto.Changeset{}} = Auth.create_user(@invalid_attrs)
    end

    test "update_user/2 with valid data updates the user" do
      user = user_fixture()
      assert {:ok, %User{} = user} = Auth.update_user(user, @update_attrs)
      assert user.confirmed_at == ~N[2011-05-18 15:01:01]
      assert user.email == "updated@email.com"
      assert user.name == "updated name"
      assert Bcrypt.verify_pass("updated.password", user.password_digest)
    end

    test "update_user/2 with invalid data returns error changeset" do
      user = user_fixture()
      assert {:error, %Ecto.Changeset{}} = Auth.update_user(user, @invalid_attrs)
      assert %User{user | password: nil} == Auth.get_user!(user.id)
    end

    test "delete_user/1 deletes the user" do
      user = user_fixture()
      assert {:ok, %User{}} = Auth.delete_user(user)
      assert_raise Ecto.NoResultsError, fn -> Auth.get_user!(user.id) end
    end

    test "change_user/1 returns a user changeset" do
      user = user_fixture()
      assert %Ecto.Changeset{} = Auth.change_user(user)
    end
  end
end

CORS (Cross-Origin Resource Sharing)

API'ımıza hangi originlerden ve hangi methodlarla erişilebileceğini belirtmek ve ya kısıtlamak için mix.exse cors_plug paketini ekliyoruz.

{:cors_plug, "~> 2.0"}

Ve ardından mix deps.get ile paketi indirirken config/dev.exs ve config/prod.exs dosyalarında aşağıdaki tanımlamaları yapıyoruz.

# config/dev.exs

config :cors_plug,
  origin: ~r/localhost/,
  max_age: 86400, # 24 hours
  methods: ["GET", "POST", "PUT", "PATCH", "DELETE"]

# config/prod.exs

config :cors_plug,
  origin: ["https://domain.com", "https://example.com"],
  max_age: 86400, # 24 hours
  methods: ["GET", "POST", "PUT", "PATCH", "DELETE"]

İlk endpointimiz

Phoenix de aynı Rails gibi güzel generatorlere(mix tasks) sahip. Json endpointleri hazırlamak için aşağıdaki mix taskını kullanabiliriz.

mix phx.gen.json Auth User users name:string email:string password:string --no-context --no-schema

# it asks for proceed that with existed Auth context, you can say [Y]
    * creating lib/app_web/controllers/user_controller.ex
    * creating lib/app_web/views/user_view.ex
    * creating test/app_web/controllers/user_controller_test.exs
    * creating lib/app_web/views/changeset_view.ex
    * creating lib/app_web/controllers/fallback_controller.ex

    Add the resource to your :api scope in lib/app_web/router.ex:

        resources "/users", UserController, except: [:new, :edit]

router.ex dosyamıza rotamızı ekleyelim.

scope "/api", AppWeb do
  pipe_through :api
  ...
  scope "/v1", V1 do
    resources "/users", UserController, except: [:new, :edit]
  end
end

Tarayıcımızdan http://localhost:4000/api/v1/users adresine gidersek yeni rotamızı görebiliriz.

Tabi user_controller_test dosyamızı da testleri geçecek şekilde güncellemeyi de ihmal etmeyelim. Özellikle password fieldını gördüğümüz her yerden kaldıralım.

lib/app_web/views/user_view.ex dosyasında da password ü kaldırmayı unutmayalım.

Not: Bu yazıyı beğendiyseniz medium profilimden de yerebilir ve ya övebilirsiniz...

İlgili Yazılar

Rehber: Phoenix API uygulaması #2

murat

İlk bölümünü şurada yayınladığım serisinin ikinci yazısıdır.

Bir önceki bölümde basitçe crud işlemleri yapan bir endpoint hazırlamıştık. Tabi ki sırada bir API ın olmazsa olmazı authentication işlemlerini nasıl yapacağımızdan bahsedeceğim.

Öncelikle ihtiyaç duyacağımız paketlerden bahsedelim. Ruby yazanlar ve Rails ile uygulama geliştirenler bilirler plataformatec'in geliştirdiği ve José...

Google Görüşme Üniversitesi: Web Geliştiricisinin Yazılım Mühendisliğine Yolculuğu

butch

John Washam, eğitimini ekonomi alanında tamamlamış ama kariyerini web programcısı olarak yapmış biri. Başarılı olan bir kaç iş de kurmuş. Hayatını bu alandan kazanıyor. Kariyerini, çocukluğunda hayal ettiği yazılım geliştiricilik üzerine kurduğunu, iyi bir yazılımcı olduğunu düşünürken bunun doğru olmadığı gerçeğiyle yüzleşmiş. Bundan birkaç yıl önce iş aramaya karar verdiğinde, aslında...

Touch Board ve Elektronik Boya ile içinizdeki sanatçıyı ortaya çıkarın

butch

Bare Conductive, Touch Board ile 2013 yılı sonunda Kickstarter'dan başarıyla çıkmıştı. Bundan yaklaşık 1 yıl sonra edindiğim bu Arduino uyumlu, kapasitif dokunma, mesafe algılama, MP3 çalar, MIDI özellikleri bulunan cihaz, firmanın bir diğer ürünü elektrik paint ile birlikte oldukça eğlenceli saatler geçirmemi sağlamıştı. Elektronik-Yazılım-Sanat alanında üretime pratik bir zemin sağlayan,...

Kodlamanın da saati mi olurmuş?!? Olurmuş...

eph

Her çocuğa bilgisayar bilimlerini sevdirecek, düşünmelerini ve hatta kendi oyunlarını tasarlamalarını sağlayacak bir platformdan bahsetmek istiyorum... code.org.. Yeni değil, bilen bilir.. 2. sınıf ve üzerindeki her yaş grubuna hitap eden farklı zorluk seviyelerinde ve farklı temalarda görevleri yerine getirme "oyun"u.. Oyun kelimesini özellikle kullandım çünkü çocuklar (öğrencilerim)...

Geleneksel üniversiteye mecbur muyuz?

butch

Özellikle ülkemizde, sıkça kulak misafiri olduğumuz konulardır, "Kendime uygun yüksek lisans/doktora programı bulamıyorum", "GPA'm çok düşük beni o programa kabul etmezler", "Yüksek lisansla birlikte götüremiyorum, işi bıraktım" ve türevleri. Acaba bu durumu gerçekten sorguluyor muyuz yoksa hala eski alışkanlıkların kurbanı olmaya devam mı ediyoruz.

Günümüzde...