Разработка бэкенда сайта на Elixir (Phoenix)

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка бэкенда сайта на Elixir (Phoenix)
Сложная
от 2 недель до 3 месяцев
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Разработка бэкенда сайта на Elixir (Phoenix)

Phoenix — фреймворк на Elixir, который строится поверх BEAM (Erlang VM). BEAM изначально создавался для телекоммуникаций с требованием девяти девяток uptime. Это определяет характер платформы: миллионы лёгких процессов, изолированных друг от друга, встроенные механизмы восстановления после сбоев, hot code reloading в production.

Где это имеет смысл

Чаты, системы уведомлений реального времени, IoT-бэкенды, игровые серверы, финансовые системы с требованиями к отказоустойчивости — BEAM здесь в своей стихии. WhatsApp держал 900 миллионов пользователей на 50 инженерах, во многом благодаря Erlang. Phoenix добавляет к этому удобный веб-слой с каналами, LiveView и Ecto.

Структура проекта

mix phx.new my_app --no-html --database postgres
cd my_app
mix deps.get
mix ecto.setup
# lib/my_app_web/router.ex
defmodule MyAppWeb.Router do
  use MyAppWeb, :router

  pipeline :api do
    plug :accepts, ["json"]
    plug MyAppWeb.Plugs.AuthPipeline
  end

  scope "/api/v1", MyAppWeb do
    pipe_through :api

    resources "/users", UserController, only: [:index, :show, :create, :update, :delete]
    resources "/orders", OrderController, except: [:new, :edit]
    post "/auth/login", AuthController, :login
    post "/auth/refresh", AuthController, :refresh
  end
end

Ecto — схемы и запросы

# lib/my_app/accounts/user.ex
defmodule MyApp.Accounts.User do
  use Ecto.Schema
  import Ecto.Changeset

  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id

  schema "users" do
    field :email, :string
    field :name, :string
    field :password, :string, virtual: true
    field :password_hash, :string
    field :role, Ecto.Enum, values: [:user, :moderator, :admin], default: :user

    has_many :orders, MyApp.Orders.Order
    timestamps()
  end

  def registration_changeset(user, attrs) do
    user
    |> cast(attrs, [:email, :name, :password])
    |> validate_required([:email, :name, :password])
    |> validate_format(:email, ~r/^[^\s]+@[^\s]+$/, message: "invalid format")
    |> validate_length(:password, min: 8)
    |> unique_constraint(:email)
    |> put_password_hash()
  end

  defp put_password_hash(%Ecto.Changeset{valid?: true, changes: %{password: pw}} = cs) do
    change(cs, Argon2.add_hash(pw))
  end
  defp put_password_hash(cs), do: cs
end
# lib/my_app/orders.ex — контекст
defmodule MyApp.Orders do
  import Ecto.Query
  alias MyApp.{Repo, Orders.Order}

  def list_for_user(user_id, opts \\ []) do
    page = Keyword.get(opts, :page, 1)
    per_page = Keyword.get(opts, :per_page, 25)

    Order
    |> where(user_id: ^user_id)
    |> order_by(desc: :inserted_at)
    |> preload(:items)
    |> Repo.paginate(page: page, page_size: per_page)
  end

  def create_order(user, attrs) do
    Ecto.Multi.new()
    |> Ecto.Multi.insert(:order, Order.changeset(%Order{user_id: user.id}, attrs))
    |> Ecto.Multi.run(:payment, fn _repo, %{order: order} ->
      MyApp.Payments.charge(order)
    end)
    |> Repo.transaction()
    |> case do
      {:ok, %{order: order}} -> {:ok, order}
      {:error, :order, changeset, _} -> {:error, changeset}
      {:error, :payment, reason, _} -> {:error, reason}
    end
  end
end

Phoenix Channels — WebSocket в реальном времени

# lib/my_app_web/channels/room_channel.ex
defmodule MyAppWeb.RoomChannel do
  use Phoenix.Channel
  alias MyApp.Messages

  def join("room:" <> room_id, _params, socket) do
    if authorized?(socket, room_id) do
      {:ok, assign(socket, :room_id, room_id)}
    else
      {:error, %{reason: "unauthorized"}}
    end
  end

  def handle_in("new_message", %{"body" => body}, socket) do
    case Messages.create(socket.assigns.current_user, socket.assigns.room_id, body) do
      {:ok, message} ->
        broadcast!(socket, "new_message", %{
          id: message.id,
          body: message.body,
          user: message.user.name,
          inserted_at: message.inserted_at
        })
        {:noreply, socket}

      {:error, _changeset} ->
        {:reply, {:error, %{reason: "invalid message"}}, socket}
    end
  end

  defp authorized?(socket, room_id) do
    # проверка прав пользователя на комнату
    MyApp.Rooms.member?(room_id, socket.assigns.current_user.id)
  end
end

GenServer для состояния

# lib/my_app/rate_limiter.ex
defmodule MyApp.RateLimiter do
  use GenServer

  @window_ms 60_000
  @max_requests 100

  def start_link(_opts) do
    GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
  end

  def check(key) do
    GenServer.call(__MODULE__, {:check, key})
  end

  def init(state), do: {:ok, state}

  def handle_call({:check, key}, _from, state) do
    now = System.monotonic_time(:millisecond)
    window_start = now - @window_ms

    requests = Map.get(state, key, [])
    recent = Enum.filter(requests, &(&1 > window_start))

    if length(recent) >= @max_requests do
      {:reply, {:error, :rate_limited}, Map.put(state, key, recent)}
    else
      {:reply, :ok, Map.put(state, key, [now | recent])}
    end
  end
end

Supervisor tree

# lib/my_app/application.ex
defmodule MyApp.Application do
  use Application

  def start(_type, _args) do
    children = [
      MyApp.Repo,
      MyAppWeb.Telemetry,
      {Phoenix.PubSub, name: MyApp.PubSub},
      MyApp.RateLimiter,
      {MyApp.Workers.EmailWorker, []},
      MyAppWeb.Endpoint
    ]

    Supervisor.start_link(children, strategy: :one_for_one, name: MyApp.Supervisor)
  end
end

Если EmailWorker упал — Supervisor перезапускает его автоматически. Остальные процессы не затронуты.

Деплой через Mix Releases

MIX_ENV=prod mix assets.deploy
MIX_ENV=prod mix release

Dockerfile:

FROM elixir:1.16-otp-26 AS builder
WORKDIR /app
ENV MIX_ENV=prod
COPY mix.exs mix.lock ./
RUN mix deps.get --only prod
COPY . .
RUN mix compile
RUN mix release

FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y openssl libncurses5 && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder /app/_build/prod/rel/my_app ./
CMD ["bin/my_app", "start"]

Сроки

Phoenix быстр в разработке при наличии опыта с Elixir. Если команда новая в языке — закладывать 2 недели на освоение Ecto, паттернов OTP и channels. API со стандартным набором CRUD + WebSocket + фоновые задачи: 3–4 недели. Высоконагруженная система с кластеризацией через libcluster и Horde: 5–8 недель вместе с нагрузочным тестированием.