Squid
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.
Why Squid?
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
Features
- 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
Installation
Add squid to your dependencies in mix.exs:
def deps do
[
{:squid, "~> 0.2.0"}
]
endQuick Start
1. Configure Your Tentacles
Register all tentacles in your main application config:
# config/config.exs
config :squid,
tentacles: [:shop, :admin, :billing]2. Create a Tentacle Router
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.Router3. Add Squid Router to Your Endpoint
Replace 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
endConfigurable Scopes
Define 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
endDynamic Tentacle Names
Use 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-system
The tentacle name is automatically converted from snake_case to kebab-case.
Partials System
Build composable UI components by aggregating partials from multiple tentacles.
Define Partials in 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
endRender Aggregated Partials
<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.
Documentation
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 docsUse Cases
Multi-Tenant Applications
Deploy the same tentacle code with different URL prefixes per tenant:
config :squid,
scopes: [
tenant_a: [prefix: "/tenant-a"],
tenant_b: [prefix: "/tenant-b"]
]Microservices-Style Monolith
Organize your application into independent services while keeping them in a single runtime:
config :squid,
tentacles: [:auth, :payments, :notifications, :analytics]Domain-Driven Design
Structure your application by business domains, each with its own router and views:
config :squid,
tentacles: [:orders, :inventory, :shipping, :billing]Development Status
Squid is currently in active development. While functional, the API may change before reaching v1.0. Feedback and contributions are welcome!
License
MIT License - see LICENSE.md for details.