Zornux docs
Get started Spec

Data & Enterprise

Application Layer

Zornux ships the building blocks of a layered backend as language constructs — DTOs with validation, services and repositories as injectable components, and a native dependency-injection container. No frameworks, no attributes, no wiring code.

Records (DTOs) & validation

A record declares the shape of data crossing a boundary — what a client may send — with declarative validation rules right on each field:

zornux
record SignupRequest
    has username
        required
        minimum length 3
        maximum length 20
    has email
        required
        email
    has age
        minimum 18
end

Check an instance with validate. The result exposes is_valid and a list of errors, one per broken field:

zornux
create bad from SignupRequest
bad.username = "a"
bad.email = "nope"
bad.age = 15

create result = validate bad
show "valid: " + text(result.is_valid)
for each problem in result.errors
    show problem.field + " " + problem.message
end
RuleChecks
requiredThe field must be present and non-empty.
minimum length N / maximum length NText length bounds.
minimum N / maximum N / range A to BNumeric bounds.
email / urlWell-formed email / URL.
matches "pattern"Matches a pattern.
validate is contextual

validate x (record validation) and validate(x) (the security built-in that tests trust) are told apart by the syntax — validate is not a reserved word.

Services & repositories

A repository holds data access; a service holds business rules and depends on repositories. Methods are functions (often async function), called as Component.method(…):

zornux
repository UserRepository
    async function save with user
        save user into AppDb.Users
        give back user
    end
end

service UserService
    use UserRepository
    async function create_user with request
        create user from User
        user.name = request.name
        user.email = request.email
        give back wait for UserRepository.save(user)
    end
end
Reserved verbs are fine as method names

A component method may be named with a reserved word — e.g. async function save — because it's only ever called as UserRepository.save(…), never as a bare statement.

Dependency injection

A component declares what it needs with use. The application … end block is the composition root — it registers every component, and the container resolves the graph:

zornux
application
    use UserRepository
    use UserService
    use UserApi
end

Registrations may declare a lifetime after assingleton (the default), scoped, or transient. A missing or cyclic dependency is caught up front (ZX2503).

Web integration — it all connects

A controller route can take a DTO directly: on POST "/" with CreateUserRequest request deserializes the JSON body, validates it against the record's rules (answering 400 on failure), and injects the services the controller declared — automatically.

zornux
controller Users at "/users"
    use UserService

    on POST "/" with CreateUserRequest request
        create created = wait for UserService.create_user(request)
        give back created message "Created " + created.name
    end
end

web UserApp
    use Users
end

publish UserApp on port 8080

That's the full path — HTTP request → validation → service → repository → ORM → database — with security threaded through, and no glue code.

Diagnostics are ZX2500ZX2599. Next: settings and secrets — Configuration.