Project Structure
This guide explains the directory structure and file organization of Azu projects created with the CLI. Understanding this structure will help you navigate and develop your applications more effectively.
Overview
Azu follows a convention-over-configuration approach with a well-defined directory structure that promotes maintainability, testability, and scalability.
Complete Project Structure
When you create a new Azu project with azu new my_app
, you get this structure:
my_app/
├── 📁 src/ # Application source code
│ ├── 📄 my_app.cr # Main application module
│ ├── 📄 server.cr # HTTP server configuration
│ ├── 📁 endpoints/ # HTTP endpoints (controllers)
│ │ └── 📁 welcome/
│ │ └── 📄 index_endpoint.cr
│ ├── 📁 models/ # Database models (CQL/Jennifer)
│ │ └── 📄 your_models_goes_here.txt
│ ├── 📁 contracts/ # Request/response contracts
│ │ └── 📁 welcome/
│ │ └── 📄 index_contract.cr
│ ├── 📁 pages/ # Page components (views)
│ │ └── 📁 welcome/
│ │ └── 📄 index_page.cr
│ ├── 📁 services/ # Business logic services
│ ├── 📁 middleware/ # HTTP middleware components
│ ├── 📁 components/ # Reusable live components
│ ├── 📁 validators/ # Custom validation logic
│ ├── 📁 initializers/ # Application startup configuration
│ │ ├── 📄 database.cr
│ │ └── 📄 logger.cr
│ └── 📁 db/ # Database-related files
│ ├── 📄 schema.cr # Database schema definition
│ ├── 📄 seed.cr # Sample data seeding
│ ├── 📁 migrations/ # Database migrations
│ └── 📄 README.md
├── 📁 spec/ # Test files
│ ├── 📄 my_app_spec.cr # Main application tests
│ ├── 📄 spec_helper.cr # Test configuration
│ ├── 📁 endpoints/ # Endpoint tests
│ ├── 📁 models/ # Model tests
│ ├── 📁 services/ # Service tests
│ └── 📁 support/ # Test support files
├── 📁 public/ # Static assets
│ ├── 📁 assets/ # CSS, JS, images
│ │ ├── 📁 css/
│ │ │ ├── 📄 bootstrap.min.css
│ │ │ └── 📄 cover.css
│ │ └── 📁 js/
│ │ └── 📄 bootstrap.min.js
│ └── 📁 templates/ # Jinja2 templates
│ ├── 📄 layout.jinja # Base layout template
│ ├── 📁 helpers/ # Partial templates
│ │ └── 📄 _nav.jinja
│ └── 📁 welcome/
│ └── 📄 index_page.jinja
├── 📁 tasks/ # Custom task definitions
│ └── 📄 taskfile.cr
├── 📁 config/ # Configuration files
├── 📄 shard.yml # Crystal dependencies
├── 📄 README.md # Project documentation
└── 📄 LICENSE # License file
Directory Details
/src
- Application Source Code
/src
- Application Source CodeThe heart of your application where all Crystal source code lives.
Main Files
my_app.cr
: Main application module that defines routes, middleware, and configurationserver.cr
: HTTP server startup and configuration
/src/endpoints
- HTTP Endpoints (Controllers)
/src/endpoints
- HTTP Endpoints (Controllers)Contains the HTTP request handlers, similar to controllers in other frameworks.
Structure:
endpoints/
├── users/
│ ├── index_endpoint.cr # GET /users
│ ├── show_endpoint.cr # GET /users/:id
│ ├── new_endpoint.cr # GET /users/new
│ ├── create_endpoint.cr # POST /users
│ ├── edit_endpoint.cr # GET /users/:id/edit
│ ├── update_endpoint.cr # PUT/PATCH /users/:id
│ └── destroy_endpoint.cr # DELETE /users/:id
└── api/
└── v1/
└── users/
└── index_endpoint.cr
Example Endpoint:
class Users::IndexEndpoint
include Azu::Endpoint
def call(request)
users = User.all
index_page = Users::IndexPage.new(users: users)
index_page.render
end
end
/src/models
- Database Models
/src/models
- Database ModelsContains your data models using CQL ORM or Jennifer ORM.
Example Model:
require "cql"
class User < CQL::Model
db_table "users"
field name : String
field email : String
field created_at : Time = Time.utc
validate :name, presence: true
validate :email, presence: true, format: EMAIL_REGEX
has_many :posts, dependent: :destroy
end
/src/contracts
- Request/Response Contracts
/src/contracts
- Request/Response ContractsType-safe request validation and response formatting.
Example Contract:
struct Users::CreateContract
include Azu::Request
validate name, presence: true, length: {min: 2, max: 50}
validate email, presence: true, format: EMAIL_REGEX
validate age, numericality: {greater_than: 0, less_than: 150}
end
/src/pages
- Page Components (Views)
/src/pages
- Page Components (Views)Render HTML responses using templates.
Example Page:
class Users::IndexPage
include Azu::Page
def initialize(@users : Array(User))
end
def render
template("users/index_page.jinja", {
"users" => @users.map(&.to_h),
"title" => "All Users"
})
end
end
/src/services
- Business Logic Services
/src/services
- Business Logic ServicesEncapsulate complex business logic following Domain-Driven Design principles.
Example Service:
class UserRegistrationService
def initialize(@email : String, @name : String)
end
def call
return Result.error("Email already exists") if User.exists?(email: @email)
user = User.create!(name: @name, email: @email)
EmailService.send_welcome_email(user)
Result.success(user)
end
end
/src/middleware
- HTTP Middleware
/src/middleware
- HTTP MiddlewareCustom middleware for request/response processing.
Example Middleware:
class AuthenticationMiddleware
include HTTP::Handler
def call(context)
if authenticated?(context)
call_next(context)
else
context.response.status = HTTP::Status::UNAUTHORIZED
context.response.print("Authentication required")
end
end
private def authenticated?(context)
context.request.headers["Authorization"]?.try(&.starts_with?("Bearer "))
end
end
/src/components
- Live Components
/src/components
- Live ComponentsReal-time interactive components for dynamic user interfaces.
Example Component:
class CounterComponent
include Azu::Component
def initialize(@count : Int32 = 0)
end
def increment
@count += 1
update_element("counter-value", @count.to_s)
broadcast_update({type: "counter_updated", count: @count})
end
def render
%(<div id="counter">
<span id="counter-value">#{@count}</span>
<button onclick="counter.increment()">+</button>
</div>)
end
end
/src/validators
- Custom Validators
/src/validators
- Custom ValidatorsReusable validation logic for models and contracts.
Example Validator:
class EmailValidator < CQL::Validator
def validate(record, attribute, value)
unless value.to_s.matches?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
record.errors.add(attribute, "is not a valid email address")
end
end
end
/src/initializers
- Application Initializers
/src/initializers
- Application InitializersConfigure various aspects of your application during startup.
Database Initializer (database.cr
):
require "cql"
CQL.setup do |config|
config.database_url = ENV.fetch("DATABASE_URL", "postgres://localhost/my_app_development")
config.log_level = :debug
end
/src/db
- Database Files
/src/db
- Database Filesschema.cr
: Database schema definitionseed.cr
: Sample data for developmentmigrations/
: Database migration files
/spec
- Test Files
/spec
- Test FilesMirror the structure of /src
for organized testing.
Example Test:
require "./spec_helper"
describe User do
describe "#valid?" do
it "is valid with valid attributes" do
user = User.new(name: "John Doe", email: "john@example.com")
user.valid?.should be_true
end
it "is invalid without a name" do
user = User.new(email: "john@example.com")
user.valid?.should be_false
end
end
end
/public
- Static Assets
/public
- Static Assets/assets
: CSS, JavaScript, images, fonts/templates
: Jinja2 templates for HTML rendering
Template Structure:
templates/
├── layout.jinja # Base layout
├── helpers/
│ ├── _nav.jinja # Navigation partial
│ ├── _footer.jinja # Footer partial
│ └── _flash.jinja # Flash messages
└── users/
├── index_page.jinja # Users listing
├── show_page.jinja # User details
└── form.jinja # User form partial
/tasks
- Custom Tasks
/tasks
- Custom TasksDefine custom command-line tasks for your application.
Example Task:
# tasks/data_import.cr
task "data:import", "Import data from CSV file" do |args|
CSV.each_row(File.open("data.csv")) do |row|
User.create!(name: row[0], email: row[1])
end
puts "Data imported successfully!"
end
File Naming Conventions
General Rules
Files:
snake_case.cr
Classes:
PascalCase
Methods/Variables:
snake_case
Constants:
SCREAMING_SNAKE_CASE
Specific Conventions
Endpoints
File:
src/endpoints/users/index_endpoint.cr
Class:
Users::IndexEndpoint
Models
File:
src/models/user.cr
Class:
User
Services
File:
src/services/user_registration_service.cr
Class:
UserRegistrationService
Contracts
File:
src/contracts/users/create_contract.cr
Struct:
Users::CreateContract
Pages
File:
src/pages/users/index_page.cr
Class:
Users::IndexPage
Components
File:
src/components/counter_component.cr
Class:
CounterComponent
Middleware
File:
src/middleware/authentication_middleware.cr
Class:
AuthenticationMiddleware
Tests
File:
spec/models/user_spec.cr
Describes:
User
Configuration Files
shard.yml
- Dependencies
shard.yml
- Dependenciesname: my_app
version: 0.1.0
dependencies:
azu:
github: azutoolkit/azu
version: ~> 1.0.0
cql:
github: azutoolkit/cql
version: ~> 0.8.0
development_dependencies:
ameba:
github: crystal-ameba/ameba
version: ~> 1.4.0
Environment Configuration
Create environment-specific configuration:
# config/environments/development.cr
Azu.configure do |config|
config.debug = true
config.log_level = :debug
config.host = "localhost"
config.port = 3000
end
# config/environments/production.cr
Azu.configure do |config|
config.debug = false
config.log_level = :info
config.host = "0.0.0.0"
config.port = ENV.fetch("PORT", "8080").to_i
end
Best Practices
Organization
Group related files in subdirectories
Mirror test structure in
/spec
Use descriptive names for files and classes
Keep files focused on single responsibilities
Dependencies
Explicit imports in each file
Group imports by source (stdlib, shards, local)
Avoid circular dependencies
Example Import Organization:
# Standard library imports
require "json"
require "uuid"
# Third-party shard imports
require "cql"
require "azu"
# Local imports
require "../models/user"
require "../contracts/base_contract"
Testing
Test files mirror source structure
Use descriptive test names
Group related tests in nested
describe
blocksTest both success and failure scenarios
Working with the Structure
Adding New Features
Generate scaffolding:
azu generate scaffold Feature name:string
Implement business logic in services
Add validations in models and contracts
Create custom middleware for cross-cutting concerns
Write comprehensive tests
Refactoring
Extract shared logic into services
Create reusable components for common UI patterns
Use modules for shared behavior
Keep controllers thin by moving logic to services
This structure provides a solid foundation for building maintainable, testable, and scalable Azu applications. The conventions help maintain consistency across projects and make it easier for team members to navigate the codebase.
Next Steps:
Command Reference - Learn about CLI commands
Generators Guide - Generate code efficiently
Development Workflows - Common development patterns
Last updated