DEV Community

tengxgfyrz67s
tengxgfyrz67s

Posted on

Project-Directory-Structure

Project Directory Structure

Project Code:https://github.com/hyperlane-dev/hyperlane

When building a production-grade web application with hyperlane, organizing your codebase effectively is just as important as writing good code. A well-structured project directory makes your codebase easier to navigate, test, and maintain. This article explores the recommended directory structure for hyperlane projects and explains the purpose of each layer.

Overview

Hyperlane follows a layered architecture pattern that separates concerns into distinct directories. The recommended structure looks like this:

├── application/controller/domain/exception/mapper/middleware/model/repository/service/utils/view
├── bootstrap/application/framework
├── config/application/framework
├── plugin/database/env/logger/mysql/postgresql/process/redis
├── resources/docker/env/sql/static/templates
Enter fullscreen mode Exit fullscreen mode

This structure is inspired by Domain-Driven Design (DDD) principles and is suitable for projects of any size — from small APIs to large enterprise applications.

The Application Layer

The application directory is the heart of your project. It contains all the business logic, organized into several subdirectories:

Controller

The controller directory contains your route handlers — the entry points for HTTP requests. Each controller corresponds to a specific resource or domain concept. Controllers are responsible for receiving requests, extracting parameters, calling the appropriate services, and returning responses.

In hyperlane, controllers are typically implemented as structs with the #[route] attribute:

#[route("/test/{text}")]
struct Route;
Enter fullscreen mode Exit fullscreen mode

Controllers should be thin — they should not contain business logic. Instead, they delegate to the service layer.

Domain

The domain directory holds your core business entities, value objects, and domain services. These are the fundamental building blocks of your application that represent the business concepts you're modeling.

Model

The model directory contains data transfer objects (DTOs), request/response models, and any data structures that represent the shape of data flowing through your application. These models are used to deserialize request bodies and serialize response data.

let json_body: T = ctx.get_request().get_body_json::<T>();
Enter fullscreen mode Exit fullscreen mode

Service

The service directory is where the bulk of your business logic lives. Services orchestrate operations, enforce business rules, and coordinate between different parts of your application. They act as the bridge between the controller layer and the repository layer.

Repository

The repository directory contains data access logic — code that interacts with databases, caches, or external APIs. Repositories abstract away the details of data storage and retrieval, providing a clean interface for the service layer to consume.

Middleware

The middleware directory houses custom middleware components. In hyperlane, middleware is implemented using the ServerHook trait:

struct RequestMiddleware;
impl ServerHook for RequestMiddleware {
    async fn new(stream: &mut Stream, _: &mut Context) -> Self { Self }
    async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
        ctx.get_mut_response().set_version(HttpVersion::Http1_1).set_status_code(200);
        Status::Continue
    }
}
server.request_middleware::<RequestMiddleware>();
Enter fullscreen mode Exit fullscreen mode

Common middleware use cases include authentication, logging, CORS handling, and request validation.

Exception and Mapper

The exception directory contains custom error types and error handling logic. The mapper directory contains functions that transform between different data representations — for example, mapping database rows to domain models.

Utils

The utils directory is for shared utility functions and helpers that don't fit neatly into any other category. This might include string manipulation, date formatting, or cryptographic helpers.

View

The view directory contains template files and view-related logic for applications that render HTML or other formatted output.

The Bootstrap Layer

The bootstrap directory contains application initialization and startup code. This is where you configure and launch your hyperlane server:

#[tokio::main]
async fn main() {
    let mut server: Server = Server::default();
    let server_control_hook: ServerControlHook = server.run().await.unwrap_or_default();
    server_control_hook.wait().await;
}
Enter fullscreen mode Exit fullscreen mode

The bootstrap/application subdirectory contains application-specific initialization logic, while bootstrap/framework contains framework-level configuration.

The Configuration Layer

The config directory stores all configuration files and configuration-loading logic. Hyperlane supports JSON-based configuration:

let config_json = r#"{ "address": "0.0.0.0:80", "nodelay": true, "ttl": 64 }"#;
let mut server = Server::default();
server.config_from_json(config_json);
Enter fullscreen mode Exit fullscreen mode

The config/application subdirectory holds application-level settings, while config/framework holds framework-level settings like server timeouts and buffer sizes.

The Plugin Layer

The plugin directory contains integrations with external systems and services:

  • database/ — Database connection pools and query builders
  • mysql/ — MySQL-specific database drivers and configurations
  • postgresql/ — PostgreSQL-specific database drivers and configurations
  • redis/ — Redis cache integration
  • logger/ — Logging framework configuration
  • env/ — Environment variable management
  • process/ — Process management utilities

The Resources Layer

The resources directory contains non-code assets:

  • docker/ — Docker configuration files and Dockerfiles
  • env/ — Environment configuration files
  • sql/ — SQL migration files and seed data
  • static/ — Static assets like CSS, JavaScript, and images
  • templates/ — HTML or text templates

Request Configuration

Hyperlane's RequestConfig allows you to fine-tune how the server handles incoming requests:

let request_config_json = r#"{ "buffer_size": 8192, "max_path_size": 8192, "max_header_count": 100, "max_header_key_size": 8192, "max_header_value_size": 8192, "max_body_size": 2097152, "read_timeout_ms": 6000 }"#;
let request_config = RequestConfig::from_json(request_config_json).unwrap();
Enter fullscreen mode Exit fullscreen mode

These settings should be configured in the config layer and applied when creating the server.

Server Configuration

Similarly, ServerConfig controls server-level behavior:

let mut config: ServerConfig = ServerConfig::default();
config.set_address("0.0.0.0:80");
config.set_nodelay(Some(true));
config.set_ttl(Some(128));
Enter fullscreen mode Exit fullscreen mode

Putting It All Together

A typical hyperlane application entry point ties all these layers together:

#[hyperlane(server: Server)]
#[hyperlane(server_config: ServerConfig)]
#[tokio::main]
async fn main() {
    // Load configuration
    server.server_config(server_config);

    // Register routes
    server.route::<Route>("/test");

    // Register middleware
    server.request_middleware::<RequestMiddleware>();
    server.response_middleware::<ResponseMiddleware>();

    // Start server
    let server_control_hook = server.run().await.unwrap_or_default();
    server_control_hook.wait().await;
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  1. Keep controllers thin — Business logic belongs in the service layer, not in controllers.
  2. Use the repository pattern — Abstract data access behind repository interfaces to make testing easier.
  3. Separate configuration from code — Use JSON configuration files for environment-specific settings.
  4. Leverage middleware — Cross-cutting concerns like authentication and logging should be implemented as middleware.
  5. Follow the single responsibility principle — Each module should have one clear purpose.
  6. Use attribute macros — Reduce boilerplate with hyperlane's powerful attribute macro system.

Conclusion

The recommended directory structure for hyperlane projects promotes clean separation of concerns, making your codebase easier to understand, test, and maintain. By organizing your code into application, bootstrap, config, plugin, and resource layers, you create a solid foundation that can scale with your project's needs. As you become more familiar with hyperlane, you can adapt this structure to fit your specific requirements while maintaining the core principles of clean architecture.


Project Code:https://github.com/hyperlane-dev/hyperlane

Top comments (0)