A modular Phoenix framework for building applications with independent, composable sub-applications called tentacles.
Squid enables you to organize your Phoenix application into smaller, focused contexts that can be developed, tested, and deployed independently while sharing a common runtime. Each tentacle defines its own routes, controllers, and views, with the ability to configure different URL prefixes per deployment environment.
Phoenix's forward/4 macro doesn't work properly with LiveView because it loses important routing metadata that LiveView depends on for features like live navigation, URL generation, and route helpers. This makes it difficult to build modular Phoenix applications with multiple independent routers, especially in umbrella apps or when organizing code by domain.
Squid solves this problem by directly calling each router's __match_route__/3 function instead of using forward, preserving all the metadata that LiveView needs. This allows you to:
- Use multiple independent routers with full LiveView support
- Forward requests between routers without breaking live navigation
- Build truly modular Phoenix applications with proper encapsulation
- Organize umbrella apps where each sub-app has its own router and LiveViews
- Modular Routing: Each tentacle has its own router with configurable prefixes
- Dynamic Scopes: Deploy the same tentacle code under different URL structures
- Composable Partials: Build UI components by aggregating views from multiple tentacles
- Flexible Architecture: Support for multi-tenant, microservices-style, or domain-driven designs
- Full LiveView Support: Unlike
forward/4, Squid preserves all routing metadata for LiveView
Add squid to your dependencies in mix.exs:
def deps do
[
{:squid, "~> 0.2.0"}
]
endRegister all tentacles in your main application config:
# config/config.exs
config :squid,
tentacles: [:shop, :admin, :billing]Each tentacle defines its own router using Squid.Router:
# lib/shop/router.ex
defmodule Shop.Router do
use Squid.Router, otp_app: :shop
squid_scope "/products" do
get "/", Shop.ProductController, :index
get "/:id", Shop.ProductController, :show
end
endRegister the router in the tentacle's config:
# In shop's config
config :shop, :squid,
router: Shop.RouterReplace the standard Phoenix router with Squid.Router in your endpoint:
# lib/my_app_web/endpoint.ex
defmodule MyAppWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :my_app
# Instead of `plug MyAppWeb.Router`
plug Squid.Router
endDefine scopes to deploy tentacles with different URL prefixes:
# config/config.exs
config :squid,
scopes: [
default: [prefix: "/"],
admin: [prefix: "/admin"],
api: [prefix: "/api/v1"],
tenant: [prefix: "/{{tentacle_name}}"]
]Use scopes in your tentacle routers:
defmodule Shop.Router do
use Squid.Router, otp_app: :shop
# Public routes at /products
squid_scope "/products" do
get "/", Shop.ProductController, :index
end
# Admin routes at /admin/products
squid_scope "/products", as: :admin do
post "/", Shop.AdminController, :create
delete "/:id", Shop.AdminController, :delete
end
# API routes at /api/v1/products
squid_scope "/products", as: :api do
get "/", Shop.API.ProductController, :index
end
endUse the {{tentacle_name}} placeholder to create tenant-specific routes:
config :squid,
scopes: [
tenant: [prefix: "/{{tentacle_name}}"]
]
# In :billing_system tentacle with `as: :tenant`
# Routes will be prefixed with /billing-systemThe tentacle name is automatically converted from snake_case to kebab-case.
Build composable UI components by aggregating partials from multiple tentacles.
# apps/shop/config/config.exs
config :shop, :squid,
router: Shop.Router,
partials: %{
navigation: {Shop.Navigation, priority: 1}
}
# apps/shop/lib/shop/navigation.ex
defmodule Shop.Navigation do
@behaviour Squid.Partial
def render(assigns) do
~H"""
<a href="/products">Products</a>
"""
end
end# apps/admin/config/config.exs
config :admin, :squid,
router: Admin.Router,
partials: %{
navigation: {Admin.Navigation, priority: 2}
}
# apps/admin/lib/admin/navigation.ex
defmodule Admin.Navigation do
@behaviour Squid.Partial
def render(assigns) do
~H"""
<a href="/admin/users">Admin</a>
"""
end
end<Squid.Partial.render partial={:navigation} current_user={@current_user} />Partials are rendered in priority order (highest first), allowing you to control the composition of your UI.
For detailed documentation, see:
Squid.Router- Architecture overview and plug configurationSquid.Router.Scope- Complete guide to scopes andsquid_scope/3Squid.Partial- Composable UI partials system
Or generate documentation locally:
mix docsDeploy the same tentacle code with different URL prefixes per tenant:
config :squid,
scopes: [
tenant_a: [prefix: "/tenant-a"],
tenant_b: [prefix: "/tenant-b"]
]Organize your application into independent services while keeping them in a single runtime:
config :squid,
tentacles: [:auth, :payments, :notifications, :analytics]Structure your application by business domains, each with its own router and views:
config :squid,
tentacles: [:orders, :inventory, :shipping, :billing]Squid is currently in active development. While functional, the API may change before reaching v1.0. Feedback and contributions are welcome!
MIT License - see LICENSE.md for details.