Architecture Overview
Arkstack is organized around one idea: application structure should stay stable even when the HTTP runtime changes.
Runtime-specific pieces live in driver packages. Shared behavior lives in focused packages. App code sits above both and uses the same conventions whether the application runs on Express, H3, or a future runtime.
Architecture Layers
Application code
routes, controllers, models, views, config, commands
Shared packages
auth, notifications, view, http, common, console, contract
Runtime drivers
@arkstack/driver-express, @arkstack/driver-h3
Underlying runtime
Express, H3This split lets Arkstack keep Laravel-like project ergonomics without binding business logic to one runtime's request, response, middleware, or route implementation.
Core Principles
- Runtime details stay in drivers. Express and H3 integrations are implemented by their driver packages.
- Shared packages avoid runtime imports. Packages like auth, notifications, views, and common utilities are designed to work without importing Express or H3 directly.
- Runtime templates keep the same app shape. Routes, models, resources, config files, and commands live in predictable places across generated applications.
- Extension points are explicit. Commands, hooks, view composers, package views, and driver middleware expose the places where packages and apps can plug in.
Package Map
Contracts
@arkstack/contract defines the runtime-agnostic contracts used by Arkstack apps and drivers. It describes the integration boundary between the Arkstack app shell and each runtime driver.
Use it when building or reviewing driver-level behavior.
Common
@arkstack/common provides shared runtime utilities:
- lifecycle and network helpers
- typed model resolution through
getModel() - hashing and encryption helpers
- shared exception and error handling primitives
- process-local hooks through
Hook
Shared packages use common when they need app services without importing app files directly.
HTTP
@arkstack/http provides small request and response wrappers for framework-neutral code.
Runtime middleware can still use native Express or H3 objects. The HTTP package exists for reusable services and tests that need normalized request data.
Auth
@arkstack/auth owns framework-neutral authentication:
- credential verification
- JWT-backed personal access tokens
- temporary tokens
- current-session helpers
- two-factor authentication
- user/session contracts
Runtime-specific auth middleware lives in driver packages so each runtime can attach authenticated state naturally.
Notifications
@arkstack/notifications provides framework-neutral delivery channels:
- mail through configured transports
- SMS through Twilio or AfricasTalking transports
- database-backed in-app notifications
Notifications use app models through getModel() when persistence is needed, and use src/config/notifications.ts for driver and transport configuration.
Views
@arkstack/view wraps Edge.js with an Arkstack view factory:
view(name, data)andView.make(name, data)- local view data through
with() - global shared view data through
share() - view composers
- package-scoped views like
~package-name.mailand~org/package-name.mail - the auto-discovered
make:viewcommand
Templates render src/resources/views/welcome.edge from the / web route.
Console
@arkstack/console provides the shared console runtime and base commands for full templates.
@arkstack/console-slim provides the slim command surface used by leaner templates.
Package commands can be auto-discovered from node_modules/@arkstack/*/dist/commands/*.js, which lets packages such as filesystem and view add commands without editing the app console kernel.
Runtime Drivers
Each runtime implements the app contract through a driver package:
@arkstack/driver-express@arkstack/driver-h3
Driver packages own runtime-specific concerns:
- route binding
- middleware integration
- static asset handling
- error handling adapters
- auth middleware state attachment
- runtime-specific stubs
This keeps application files consistent while letting each runtime use its native mechanics.
Request Flow
A typical HTTP request moves through these layers:
Runtime request
-> driver middleware
-> Clear Router route binding
-> route/controller
-> shared services
-> runtime responseFor example, an authenticated route uses driver middleware to validate the bearer token through @arkstack/auth. After validation, the driver attaches authenticated state to the request context. The route can then call shared packages such as notifications or views without caring which runtime handled the request.
Extension Points
Hooks
Hook from @arkstack/common provides named callback positions.
The auth middleware exposes middleware:auth, with a runtime-agnostic context shape:
Hook.set('middleware:auth', {
before: ({ req, res }) => {},
after: ({ req, res }) => {},
error: (error, { req, res }) => {},
});View Composers
View composers attach data before rendering:
View.composer('dashboard', (view) => {
view.with('title', 'Dashboard');
});Composers can be functions, classes, or instances with a compose() method.
Package Views
Packages can ship their own resources/views directory and expose templates through tilde names:
await view('~package-name.mail');
await view('~org/package-name.mail');Console Commands
Packages can expose commands from their built dist/commands directory. The console discovers them through the shared command glob.
Template Profiles
Full
Full templates include the batteries-included app structure:
- Arkormˣ/database setup
- app models
- migrations
- controllers and resources
- auth-ready user/session models
- notification models
- view package and
welcome.edge - API and web route scaffolding
Lean
Lean templates remove database-heavy pieces for a smaller baseline:
- no Arkormˣ runtime setup
- no app model layer
- no database migrations
- no default API resource scaffolding
They keep the same runtime and console architecture where it still applies.
Why This Matters
Arkstack's architecture gives you:
- a stable project shape across runtimes
- less duplicated runtime glue
- shared auth, notifications, views, hooks, and console commands
- room for future runtime drivers without rewriting app code
- package-level extension points that can grow without centralizing everything in one package
The result is a backend stack that feels structured and familiar while still letting the runtime remain replaceable.
