Release Notes

ArikaJS 0.10.x represents a massive leap forward in our quest to combine expressive design with the raw performance of Node.js. This release introduces a fully refactored Event Dispatcher, native TypeScript optimizations, and deep improvements to our Active Record implementation.

Versioning Scheme

ArikaJS and its core packages follow Semantic Versioning (SemVer) with a unique framework approach. Major framework releases are planned bi-annually, focusing on architectural enhancements. Minor and patch releases are rolled out frequently to address bug fixes and introduce non-breaking features. You should always ensure your application's @arikajs/* dependencies are kept in sync.

ArikaJS 0.10.19 (Current)

Circular Dependency Resolution

We've completely resolved a core circular dependency issue within the DatabaseServiceProvider that was previously causing stack overflow errors during application boot. The framework now features a deeply optimized and stable Service Provider lifecycle.

Robust Monorepo Publishing

The framework's internal dependency management has been overhauled. All core packages now correctly map their workspace relationships, ensuring flawless synchronization and preventing mismatched peer dependencies when installed from NPM.

Deployment CLI Namespace Separation

The arika-deploy tool now operates within its own dedicated binary namespace (arika deploy), preventing conflicts with the main framework CLI. This guarantees smooth sailing when scaffolding new applications using npx arika new.

Note:

For a detailed, commit-by-commit changelog, please refer to our GitHub repository.

Upgrade Guide

We strive to make upgrading your ArikaJS applications as seamless as possible. This guide will walk you through transitioning your application from version 0.9.x to 0.10.x.

Upgrading to 0.10.0

Estimated Upgrade Time: 15 Minutes

We've focused deeply on maintaining backward compatibility. Most projects will only require updating package versions in their `package.json` file.

Updating Dependencies

First, update your package.json file to require the latest @arikajs components:

Terminal
npm i @arikajs/core@latest @arikajs/cli@latest

Changes to Configuration

In 0.10.x, the default configuration for the SessionManager has transitioned from local file-based storage to an optimized memory store. If you were implicitly relying on files, you must explicitly set your storage driver in config/session.ts.

Routing Array Syntax

ArikaJS 0.10 deprecates the legacy string-based controller target syntax. You must now use the array-class syntax for all route closures:

routes/web.ts
// Old (Deprecating)
Route.get('/users', 'UserController@index');

// New (Required)
Route.get('/users', [UserController, 'index']);

Contribution Guide

ArikaJS thrives because of its community. Whether you're fixing a bug, adding a new feature, or expanding our documentation, we deeply appreciate your help.

Getting Started

To contribute to ArikaJS, you should have a solid understanding of Node.js concepts, TypeScript typing structures, and the Active Record pattern. Development primarily happens in our central monorepo.

  • Fork the main repository on GitHub.
  • Clone your fork locally: git clone https://github.com/your-username/arikajs.git
  • Install dependencies using PNPM: pnpm install
  • Ensure all baseline tests pass locally: pnpm test

Bug Reports & PRs

Before opening an issue or Pull Request, please search the issue tracker to see if your bug or feature request has already been discussed.

If you are submitting a Pull Request to fix a bug, please include a dedicated automated test confirming that your fix resolves the issue. Pull requests without test coverage will be asked to provide it before an official merge.

Security Disclosures

If you discover a security vulnerability within ArikaJS, please DO NOT open a public issue. Instead, send an email to the core infrastructure team. All security vulnerabilities will be promptly addressed.

Getting Started

Installation

ArikaJS was built from the ground up to provide a robust, batteries-included experience for modern Node.js development. Getting your first project up and running takes less than a minute.

Prerequisites

  • Node.js: Version 20.11.0 or higher.
  • Package Manager: npm, pnpm, or yarn.

Creating a New Project

The easiest way to scaffold a new ArikaJS application is by using our official CLI tool. The CLI sets up the directory structure, configuration files, and TypeScript environment automatically.

Terminal
npx @arikajs/cli new my-app
cd my-app
npm install

Starting the Development Server

Once your dependencies are installed, you can start the high-performance local development server. ArikaJS includes built-in hot-reloading for rapid iteration.

Terminal
npm run dev

Your application will now be accessible at http://localhost:3000.

Configuration

All configuration files for the ArikaJS framework are stored in the config directory. This centralized approach ensures that settings across your entire ecosystem—from database connections to mail transports—are easy to manage and version-controlled.

Dot Notation

Access deeply nested values effortlessly using expressive dot syntax like app.timezone.

Environment Driven

Values are mapped directly from .env files, allowing different settings for Local, Staging, and Production.

Cached for Speed

Use arika config:cache in production to flatten all configs into a single high-performance file.

Type Safe

All config files are standard TypeScript modules, giving you IDE autocompletion and structural validation.

Environment Configuration

Sensitive values (like API keys) or values that change per developer should stay in the .env file. ArikaJS uses the env() helper to safely retrieve these values with optional defaults.

.env
APP_NAME=ArikaJS
APP_ENV=local
APP_KEY=base64:Abc123...
APP_DEBUG=true

DB_CONNECTION=postgres
DB_HOST=127.0.0.1
DB_PORT=5432

Accessing Configuration Values

You may easily access your configuration values from anywhere in your application using the global Config facade. If the value does not exist, the second argument will be returned as the default.

Retrieval Logic
import { Config } from 'arikajs';

// 1. Basic retrieval
const name = Config.get('app.name');

// 2. Retrieval with fallback default
const debug = Config.get('app.debug', false);

// 3. Setting values at runtime (Request-scoped)
Config.set('app.timezone', 'America/New_York');

Configuration Caching

In production, you should run the config:cache command. This will combine all of your configuration files into a single, cached file which can be loaded very quickly by the framework.

Terminal
arika config:cache   # Enable caching
arika config:clear   # Remove cached file

Directory Structure

The default ArikaJS application structure is intended to provide a great starting point for both large and small applications. Of course, you are free to organize your application however you like. ArikaJS imposes almost no restrictions on where any given class is located.

The Root Directory

The App Directory

The app directory contains the core code of your application. We will explore this directory in more detail soon; however, almost all of the classes in your application will be in this directory.

The Config Directory

The config directory, as the name implies, contains all of your application's configuration files. It's a great idea to read through all of these files and familiarize yourself with all of the options available to you.

The Database Directory

The database directory contains your database migrations and seeders. If you wish, you may also use this directory to hold an SQLite database.

The Public Directory

The public directory contains the index.html (if operating as a monolith) and your assets such as images, JavaScript, and CSS. It acts as the document root for your web server.

The Routes Directory

The routes directory contains all of the route definitions for your application. By default, ArikaJS includes several route files: web.ts, api.ts, and console.ts.

The Storage Directory

The storage directory contains your compiled TypeScript builds, framework generated files, and application caches. This directory is primarily utilized internally by the ArikaJS framework.

The Tests Directory

The tests directory contains your automated tests. ArikaJS relies heavily on native JavaScript testing assertions and robust HTTP test suites. Each test file should typically reside here.

The App Directory

The Console Directory

The Console directory contains all of the custom Arika CLI commands for your application. These commands may be generated using the make:command utility.

The Events Directory

The Events directory houses event classes. Events may be used to alert other parts of your application that a given action has occurred, providing a great deal of flexibility and decoupling.

The Exceptions Directory

The Exceptions directory contains your application's exception handler and is also a good place to place any custom exceptions generated by your application.

The Http Directory

The Http directory contains your controllers, middleware, and form requests. Almost all of the logic to handle requests entering your application will be placed here.

The Jobs Directory

The Jobs directory houses the queueable jobs for your application. Jobs may be queued by your application or run synchronously within the current request lifecycle.

The Listeners Directory

The Listeners directory contains the classes that handle your events. Event listeners receive an event instance and perform logic in response to the event being fired.

The Mail Directory

The Mail directory contains all of your classes that represent emails sent by your application. Mail objects encapsulate all the logic required to build an email into a single, simple class.

The Models Directory

The Models directory contains your Active Record models. These classes act as an elegant, object-oriented wrapper allowing you to interact with your database tables without writing raw SQL.

The Policies Directory

The Policies directory contains authorization policy classes. Policies are used to determine if a user has permission to perform a given action against a resource, working in tandem with authorization Gates.

The Providers Directory

The Providers directory contains all of the service providers for your application. Service providers bootstrap your application by binding services in the container, registering events, and executing any other tasks to prepare your application for incoming requests.

Deployment

When you're ready to deploy your ArikaJS application to production, there are a few important steps you must take to ensure your application is running as efficiently and securely as possible.

Server Requirements

  • Node.js 20+
  • A Process Manager (e.g., PM2)
  • A Reverse Proxy (e.g., Nginx or Caddy)

Optimization Steps

1. Compiling TypeScript

ArikaJS applications are written in TypeScript. Before running in production, you must transpile your code to plain JavaScript for maximal execution speed and to bypass TS runtime overhead.

Terminal
npm run build

2. Environment Variables

Ensure that your production environment variables are properly set. Most crucially, the APP_ENV variable should be set to production, and APP_DEBUG MUST be set to false to prevent exposing sensitive stack traces.

3. Process Management (PM2)

Never use npm run dev in production. We strongly recommend using PM2 to keep your application alive indefinitely and to reload it without downtime.

If you are running a lightweight application or deploying to a very small server, you can run ArikaJS in standard Fork Mode (single instance):

Terminal (Fork Mode)
# Start a single instance of the app
pm2 start build/server.js --name "arika-app"

However, because Node.js is single-threaded, for production workloads you should run ArikaJS in PM2's Cluster Mode. This spawns multiple instances of your application mapped to the available CPU cores, dramatically increasing throughput.

Terminal (Cluster Mode)
# Start the app taking advantage of all CPU cores
pm2 start build/server.js --name "arika-app" -i max

4. Nginx Reverse Proxy

In a production environment, your Node.js application should not bind directly to port 80 or 443. Instead, you should place a robust web server like Nginx in front of your ArikaJS application. Nginx will handle SSL termination and serve static assets, proxying dynamic requests to your internal Node process.

Here is a basic Nginx configuration block you can use as a starting point:

/etc/nginx/sites-available/arika-app
server {
    listen 80;
    server_name example.com;
    root /var/www/my-app/public;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    index index.html;

    charset utf-8;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}
Architecture Concepts

Request Lifecycle

When using any tool in the "real world", you feel more confident if you understand how that tool works. Application development is no different. When you understand how ArikaJS functions under the hood, everything feels less "magical" and you become a more confident developer.

First Steps

The entry point for all requests to an ArikaJS application is the build/server.js file. All requests are directed to this file by your web server (Nginx/Apache) configuration. The server.js file doesn't contain much code. Rather, it is a starting point for loading the rest of the framework.

The server.js file retrieves the Arika application instance, which acts as the Service Container. Once the application instance has been created, the incoming HTTP request is handed off to the HTTP Kernel.

HTTP & Console Kernels

Next, the incoming request is sent to either the HTTP Kernel or the Console Kernel, depending on the type of request entering the application. These two kernels serve as the central location through which all requests flow.

The HTTP Kernel defines an array of bootstrappers that will be run before the request is executed. These bootstrappers configure error handling, configure logging, detect the application environment, and perform other tasks that need to be done before the request is actually handled.

Service Providers

One of the most important Kernel bootstrapping actions is loading the service providers for your application. All of the service providers for the application are configured in the config/app.ts configuration file's providers array.

First, the register method will be called on all providers, then, once all providers have been registered, the boot method will be called. This is how the framework "builds" itself up piecemeal.

Service Container

The ArikaJS service container is a powerful tool for managing class dependencies and performing dependency injection. Dependency injection is a fancy phrase that essentially means this: class dependencies are "injected" into the class via the constructor or, in some cases, "setter" methods.

Binding

Almost all of your service container bindings will be registered within service providers, so most of these examples will demonstrate using the container in that context.

Simple Bindings

Within a service provider, you always have access to the container via the this.app property. We can register a binding using the bind method, passing the class or interface name we wish to register along with a closure that returns an instance of the class:

Providers/AppServiceProvider.ts
import { Transistor } from './services/Transistor';
import { PodcastParser } from './services/PodcastParser';

this.app.bind('Transistor', (app) => {
    return new Transistor(app.make(PodcastParser));
});

Binding A Singleton

The singleton method binds a class or interface into the container that should only be resolved one time. Once a singleton binding is resolved, the same object instance will be returned on subsequent calls into the container:

Singleton Binding
this.app.singleton('Transistor', (app) => {
    return new Transistor(app.make(PodcastParser));
});

Resolving

You may use the make method to resolve a class instance from the container. The make method accepts the name of the class or interface you wish to resolve:

Resolving Dependencies
import { App } from '@arikajs/foundation';

const api = App.make('Transistor');
api.publish();

Service Providers

Service providers are the central place of all ArikaJS application bootstrapping. Your application, as well as all of Arika's core services, are bootstrapped via service providers.

But, what do we mean by "bootstrapped"? In general, we mean registering things, including registering service container bindings, event listeners, middleware, and even routes. Service providers are the central place to configure your application.

Writing A Service Provider

All service providers extend the ServiceProvider class. Most service providers contain a register and a boot method. Within the register method, you should only bind things into the service container. You should never attempt to register any listeners, routes, or any other piece of functionality.

Providers/RiakServiceProvider.ts
import { ServiceProvider } from '@arikajs/foundation';
import { RiakConnection } from './services/RiakConnection';

export class RiakServiceProvider extends ServiceProvider {
    /**
     * Register any application services.
     */
    public register() {
        this.app.singleton('Riak\\Connection', (app) => {
            return new RiakConnection(config.get('riak'));
        });
    }

    /**
     * Bootstrap any application services.
     */
    public async boot() {
        const connection = this.app.make('Riak\\Connection');
        await connection.warmUp();
    }
}

Event Dispatcher

Arika's events provide a simple observer pattern implementation, allowing you to subscribe and listen for various events that occur in your application. Event classes are typically stored in the app/Events directory, while their listeners are stored in app/Listeners.

Registering Events & Listeners

The EventServiceProvider included with your Arika application provides a convenient place to register all of your application's event listeners. The listen property contains an object that maps events to their listeners.

Providers/EventServiceProvider.ts
import { EventServiceProvider as ServiceProvider } from '@arikajs/foundation';
import { OrderShipped } from '../Events/OrderShipped';
import { SendShipmentNotification } from '../Listeners/SendShipmentNotification';

export class EventServiceProvider extends ServiceProvider {
    /**
     * The event listener mappings for the application.
     */
    protected listen = {
        [OrderShipped.name]: [
            SendShipmentNotification,
        ],
    };
}

Dispatching Events

To dispatch an event, you may call the static dispatch method on the event class itself, or use the global Event facade.

Logic
import { OrderShipped } from '../../events/OrderShipped';
import { Event } from '@arikajs/events';

// Option 1: Static dispatch
OrderShipped.dispatch(order);

// Option 2: The Event Facade
Event.dispatch(new OrderShipped(order));

Facades

Throughout the Arika documentation, you will see examples of code that interacts with Arika's features via "facades". Facades provide a "static" interface to classes that are available in the application's service container.

Facades serve as "static proxies" to underlying classes in the service container, providing the benefit of a terse, expressive syntax while maintaining more testability and flexibility than traditional static methods.

Facade vs. Dependency Injection

Consider a route that needs to access the application's Cache to check if a user is online. Let's look at how we might do this using the Facade versus traditional dependency injection.

Using Facades

0
routes/web.ts
import { Cache, Route } from 'arikajs';

Route.get('/profile/{id}', async (req, res) => {
    // Clean, terse, static syntax
    const isOnline = await Cache.get(`online_users_${req.params.id}`);
    
    res.json({ online: isOnline });
});

Using Dependency Injection

routes/web.ts
import { App } from '@arikajs/foundation';
import { CacheManager } from '@arikajs/cache';
import { Route } from 'arikajs';

Route.get('/profile/{id}', async (req, res) => {
    // More verbose, requires container resolution
    const cache = App.make(CacheManager);
    const isOnline = await cache.get(`online_users_${req.params.id}`);
    
    res.json({ online: isOnline });
});
The Basics

Routing

The most fundamental part of any ArikaJS application is routing. ArikaJS's router allows you to express your API endpoints and web views using a beautiful, simple syntax.

Basic Routing

The most basic Arika routes accept a URI and a closure, providing a very simple and expressive method of defining routes and behavior without complicated routing configuration files:

routes/web.ts
import { Route } from 'arikajs';

Route.get('/greeting', function (req, res) {
    res.send('Hello World');
});

Available Router Methods

The router allows you to register routes that respond to any HTTP verb:

HTTP Methods
Route.get('/path', callback);
Route.post('/path', callback);
Route.put('/path', callback);
Route.patch('/path', callback);
Route.delete('/path', callback);
Route.options('/path', callback);

Sometimes you may need to register a route that responds to multiple HTTP verbs. You may do so using the match method. Or, you may even register a route that responds to all HTTP verbs using the any method:

Advanced Matching
Route.match(['get', 'post'], '/path', callback);

Route.any('/path', callback);

Route Parameters

Of course, sometimes you will need to capture segments of the URI within your route. For example, you may need to capture a user's ID from the URL. You may do so by defining route parameters:

Parameter Capture
Route.get('/user/:id', function (req, res) {
    return res.send('User ' + req.params.id);
});

Named Routes

Named routes allow the convenient generation of URLs or redirects for specific routes. You may specify a name for a route by chaining the name method onto the route definition:

Named Routes
Route.get('/user/profile', [UserProfileController, 'show']).name('profile');

Route Groups

Route groups allow you to share route attributes, such as middleware, across a large number of routes without needing to define those attributes on each individual route. You can use the fluent prefix and middleware builders to structure your groups.

Route Grouping
Route.middleware([AuthMiddleware]).prefix('api/v1').group(() => {
    
    Route.get('/dashboard', [DashboardController, 'index']);
    Route.post('/settings', [SettingsController, 'update']);

});

Resource Routes

ArikaJS resource routing assigns the typical "CRUD" routes to a controller with a single line of code. The Route.resource method automatically generates index, create, store, show, edit, update, and destroy routes.

Resource Routes
// Generates 7 standard CRUD routes for the photos resource
Route.resource('photos', PhotoController);

Middleware

Middleware provide a convenient mechanism for inspecting and filtering HTTP requests entering your application. For example, ArikaJS includes a middleware that verifies the user of your application is authenticated.

Defining Middleware

To create a new middleware, you use the `ArikaCLI` make command or define a class implementing the Middleware contract. A middleware only needs a single handle method:

Http/Middleware/EnsureTokenIsValid.ts
import { Middleware } from '@arikajs/middleware';
import { Request, Response } from '@arikajs/http';

export class EnsureTokenIsValid implements Middleware {
    /**
     * Handle an incoming request.
     */
    public async handle(request: Request, next: Function, response: Response) {
        if (request.input('token') !== 'my-secret-token') {
            return response.redirect('/home');
        }

        return await next(request);
    }
}

Before & After Middleware

Whether a middleware runs before or after a request depends on the middleware itself. The EnsureTokenIsValid above runs before the application handles the request. However, this middleware would perform its task after the request is handled by the application:

Http/Middleware/AddSecurityHeader.ts
export class AddSecurityHeader implements Middleware {
    public async handle(request: Request, next: Function, response: Response) {
        const result = await next(request);
        
        // Perform action after the request is handled
        result.header('X-Strict-Transport-Security', 'max-age=31536000;');
        
        return result;
    }
}

Registering Middleware

All middleware are registered in your application's app/src/http/Kernel.ts file. This kernel is the central command post for request filtering.

Global & Group Middleware

If you want a middleware to run during every single HTTP request, list it in the middleware array. To assign middleware to specific groups of routes, use the middlewareGroups object (e.g. `web` vs `api`).

Http/Kernel.ts
export class Kernel {
    /**
     * The application's global HTTP middleware stack.
     */
    protected middleware: any[] = [
        new CorsMiddleware(),
        new SecurityHeaders(),
        new RequestLoggingMiddleware(),
        new BodyParserMiddleware(),
    ];
}

Route Middleware Aliases

To assign middleware only to specific routes, you must first register an alias for it by appending it to the routeMiddleware object in your Kernel. Once registered, use the Route.middleware() method:

Middleware Aliasing
// Inside Kernel.ts
protected routeMiddleware: Record<string, any> = {
    'auth': Authenticate,
    'verified': EnsureEmailIsVerified,
};

// Inside Routes
Route.get('/dashboard', [DashboardController, 'index'])
     .middleware(['auth']);

Middleware Parameters

Middleware can also receive additional parameters. For example, if your application needs to verify that the authenticated user has a given "role" before performing a given action, you could create an EnsureUserHasRole middleware that receives a role name as an additional argument.

Additional middleware parameters will be passed to the middleware after the response argument:

Http/Middleware/EnsureUserHasRole.ts
export class EnsureUserHasRole implements Middleware {
    public async handle(request: Request, next: Function, response: Response, role: string) {
        if (!request.user() || !request.user().hasRole(role)) {
            // Redirect or Abort...
            return response.status(403).send('Restricted Access');
        }

        return await next(request);
    }
}

Middleware parameters may be specified when defining the route by separating the middleware name and parameters with a :. Multiple parameters should be delimited by commas:

Parameter Syntax
Route.put('/post/{id}', [PostController, 'update'])
     .middleware(['role:editor']);

Controllers

Instead of defining all of your request handling logic as closures in your route files, you may wish to organize this behavior using "controller" classes. Controllers can group related request handling logic into a single class.

Writing Controllers

ArikaJS controllers are simple TypeScript classes designed for high testability. Unlike rigid frameworks, they do not need to extend a base class. ArikaJS uses an intelligent Method Invoker that automatically injects dependencies into your methods based on their signatures.

UserController.ts
import { Request, Response } from 'arikajs';
import { User } from '@Models/User';

export class UserController {
    /**
     * Show the profile for a given user.
     * Note: 'id' is automatically injected from the route URI.
     */
    public async show(req: Request, res: Response, id: string) {
        const user = await User.find(id);

        if (!user) {
            return res.status(404).send('User not found');
        }

        // ArikaJS automatically converts objects to JSON responses
        return { data: user };
    }
}

Routing to Controllers

You can route to a controller method by passing an array containing the class reference and the string method name. The router will automatically instantiate the controller and invoke the appropriate method:

routes/web.ts
import { Route } from 'arikajs';
import { UserController } from './Http/Controllers/UserController';

// The ":id" segment will be passed as the 3rd argument to UserController.show
Route.get('/user/:id', [UserController, 'show']);

Resource Controllers

If you think of each model in your application as a "resource", it is typical to perform the same sets of actions against each resource. ArikaJS simplifies this pattern using the Route.resource() generator. It binds incoming paths directly to conventional controller methods.

A typical resource controller will look like this. Note how handlers are typed and parameters are injected after the response object:

Http/Controllers/PhotoController.ts
import { Request, Response } from 'arikajs';

export class PhotoController {
    async index(req: Request, res: Response) { 
        return Photo.all(); 
    }
    
    async show(req: Request, res: Response, id: string) { 
        return Photo.findOrFail(id);
    }
    
    async update(req: Request, res: Response, id: string) { 
        const photo = await Photo.findOrFail(id);
        await photo.update(req.all());
        return photo;
    }
    
    async destroy(req: Request, res: Response, id: string) { 
        await Photo.destroy(id);
        return { deleted: true };
    }
}

Dependency Injection & Constructors

Since ArikaJS uses a Service Container to resolve controllers, you can type-hint any dependencies your controller needs in its constructor. They will be automatically resolved and injected:

Http/Controllers/UserController.ts
import { UserRepository } from '../Repositories/UserRepository';

export class UserController {
    constructor(protected users: UserRepository) {}

    public async index() {
        return await this.users.all();
    }
}

Controller Middleware

In addition to assigning middleware in your route files, you can define them directly within your controller. This is useful for keeping route logic bundled with the class. Simply define a static middleware array containing your middleware instances or aliases:

Http/Controllers/UserController.ts
export class UserController {
    // This middleware will be applied to all methods in this controller
    static middleware = ['auth', 'log'];

    public async show(req, res, id) {
        /* ... */
    }
}

HTTP Requests

ArikaJS's Request class provides an object-oriented way to interact with the current HTTP request being handled by your application as well as retrieve the input, cookies, and files that were submitted with the request.

Accessing The Request

To obtain an instance of the current HTTP request via dependency injection, you should type-hint the Request class on your controller method. The incoming request instance will automatically be injected by the Service Container:

Http/Controllers/UserController.ts
import { Request } from 'arikajs';

export class UserController {
    public async update(req: Request) {
        const name = req.input('name');
        // ...
    }
}

Request Path & Method

The Request instance provides a variety of methods for examining the incoming HTTP request:

Path & Method Methods
// Get the request path
const path = req.path(); // e.g. /user/profile

// Verify that the request path matches a pattern
if (req.is('admin/*')) {
    // ...
}

// Get the request URL (without query string)
const url = req.url();

// Get the full request URL (with query string)
const fullUrl = req.fullUrl();

// Get the HTTP verb
const method = req.method();

Input Handling

ArikaJS simplifies input retrieval by providing a unified API for query strings, form-data, and JSON payloads:

Retrieving Input
// Retrieve all input data (Body + Query + Route Params)
const all = req.all();

// Retrieve a single input value
const name = req.input('name');

// Retrieve with a default value
const name = req.input('name', 'Guest');

// Retrieve only specific keys
const subset = req.only(['username', 'password']);

// Retrieve all except specific keys
const filtered = req.except(['credit_card']);

Files & Uploads

You can retrieve uploaded files from the request using the file method. The method returns an instance of UploadedFile:

Handling Uploads
const photo = req.file('photo');

if (photo) {
    // Store the file on the 'avatars' disk with an auto-generated name
    const path = await photo.store('avatars');
    
    // Or move it manually to a directory
    await photo.move('./uploads', 'profile.jpg');
}

Headers & Cookies

Accessing request headers and cookies is straightforward using high-level helper methods:

Headers & Cookies
// Get a header
const value = req.header('X-Header-Name');

// Get a cookie value
const name = req.cookie('name');

// Determine if the request wants JSON
if (req.expectsJson()) {
    // ...
}

HTTP Responses

All routes and controllers should return a response to be sent back to the user's browser. ArikaJS provides several ways to return responses, ranging from simple strings to complex streaming templates.

Basic Responses

The most basic response is simply returning a string or an object from a route or controller. The framework will automatically convert the value into a full HTTP response:

Basic Returns
Route.get('/', () => {
    return 'Hello World'; // Sent as text/plain
});

Route.get('/api', () => {
    return { foo: 'bar' }; // Sent as application/json
});

Redirects

Redirect responses are instances of the Response class and contain the proper headers needed to redirect the user to another URL:

Redirecting
Route.get('/dashboard', (req, res) => {
    return res.redirect('/home');
});

// Redirect back to the previous location
Route.post('/user/profile', (req, res) => {
    return res.back(req);
});

JSON Responses

The json method will automatically set the Content-Type header to application/json and convert the given array/object to JSON:

JSON Response
return res.json({
    'name': 'Abigail',
    'state': 'CA',
}, 201);

Attaching Headers & Cookies

You can chain methods to the response instance to add headers or cookies before sending it:

Headers & Cookies
return res.header('Content-Type', 'text/plain')
          .header('X-Header-One', 'value')
          .cookie('name', 'value', { expiration: 3600 })
          .send('Hello World');

View Responses

If you need to return an HTML template, use the view helper. This is the most common response for web routes:

Rendering Templates
import { view } from 'arikajs';

Route.get('/profile', async (req, res) => {
    return await view('user.profile', { user: req.user() });
});

Streaming Responses

ArikaJS has first-class support for streaming, allowing you to send large files or real-time templates without buffering them in memory:

Streaming Data
import { createReadStream } from 'fs';

Route.get('/video', (req, res) => {
    const stream = createReadStream('video.mp4');
    return res.header('Content-Type', 'video/mp4').stream(stream);
});
The Basics

Views

Views contain the HTML served by your application and separate your controller / application logic from your presentation logic. ArikaJS uses a high-performance, directive-based template engine inspired by Blade.

Creating & Rendering Views

Views are typically stored in the resources/views directory. A simple view might look like this:

resources/views/greeting.ark.html
<html>
    <body>
        <h1>Hello, {{ name }}</h1>
    </body>
</html>

Since this view is stored at resources/views/greeting.ark.html, we may return it using the global view helper:

routes/web.ts
Route.get('/', () => {
    return view('greeting', { name: 'James' });
});

Directives Reference

ArikaJS provides a massive library of directives to handle everything from simple logic to advanced SPA behaviors. Here is a deep dive into every directive available in the engine.

1. Conditionals

Handle logical branching within your templates with native JavaScript performance.

Conditionals
// Basic If/Else
@if(count > 0)
    <p>Items found!</p>
@elseif(count === 0)
    <p>No items.</p>
@else
    <p>Invalid state.</p>
@endif

// Opposite of @if
@unless(user.isAdmin)
    <p>You are a guest.</p>
@endunless

// Check if variable is set and not null
@isset(user.profile)
    <img src="{{ user.profile.avatar }}">
@endisset

// Check if collection is empty (null, undefined, or length 0)
@empty(records)
    <p>No records to display.</p>
@endempty

2. Loops & Iteration

Manage lists and repetitive data with automatic loop tracking for indices, parity, and counts.

Loops
// Smart Foreach with the 'loop' tracking object
@foreach(users as user)
    <li>{{ user.name }} (Index: {{ loop.index }})</li>
@endforeach

// Forelse: Iteration + Empty fallback
@forelse(posts as post)
    <article>{{ post.title }}</article>
@empty
    <p>No posts found.</p>
@endforelse

// Optimized Partial Rendering
@each('partials.user-card', users, 'user', 'partials.no-users')

3. Server Actions Lifecycle

ArikaJS facilitates a zero-API experience. The @action directive allows you to call controller methods directly, while the actions global provides hooks for the request lifecycle.

Lifecycle Handlers
<form @action="createPost">
    <input name="title">
    <button>Submit</button>
</form>

<script>
actions.createPost
    .optimistic((data) => {
        // Immediate UI update
        UI.addLocalPost(data.title);
    })
    .start(() => {
        NProgress.start();
    })
    .finish((res) => {
        toast.success(res.message);
    })
    .error((err) => {
        alert('Action failed!');
    })
    .always(() => {
        NProgress.done();
    });
</script>

4. View Composers & Shared Data

Inject data or functionality globally into your views at the engine level.

Engine Configuration
// 1. View Composers: Run logic before a specific view renders
view.composer('dashboard', async (data) => {
    data.notifications = await Notifications.recent();
});

// 2. Global Shared Data
view.share('version', '1.0.0');

// 3. Global Helpers
view.helper('money', (val) => `$${val.toFixed(2)}`);

5. SEO & Meta Management

Declaratively manage your page's head without manual tag management.

resources/views/product.ark.html
@meta({
    title: product.name,
    description: product.summary,
    image: product.thumbnail
})

// Inside your main layout head:
<head>
    @head // Renders Title, OG, and Twitter Meta tags
</head>

6. Optimization & Async

Advanced Traits
// Native Promise Support
@await(Database.query('...'))

// Fragment rendering for partial AJAX updates
@fragment('table-rows')
    <tr>...</tr>
@endfragment

// Execution isolation
@once
    <script>console.log('Bootstrapped!');</script>
@endonce

7. Forms & Security

Secure Forms
<form action="/auth" method="POST">
    @csrf
    @method('DELETE')

    @error('email')
        <span class="text-error">{{ message }}</span>
    @enderror
</form>

// Asset generation via Vite
@vite(['resources/ts/app.ts'])

Layouts & Inheritance

Most web applications maintain the same general layout across various pages. ArikaJS makes it easy to define these layouts as a single view and extend them in your child views.

resources/views/layouts/app.ark.html
<html>
    <head>
        <title>App - @yield('title')</title>
    </head>
    <body>
        <div class="container">
            @yield('content')
        </div>
        @stack('scripts')
    </body>
</html>

The SPA Engine

ArikaJS includes a revolutionary Zero-Config SPA engine. By adding a single directive, your application transforms into a high-performance Single Page Application with instant navigation, scroll preservation, and hover prefetching.

resources/views/layouts/app.ark.html
<body>
    @spa // Activates instant navigation across your site
    @yield('content')
</body>
The Basics

Session

ArikaJS provides a high-performance, driver-based session system designed for horizontally scalable applications. Built with advanced architecture, it eliminates redundant I/O and ensures state consistency even in high-concurrency environments.

Lazy Loading

Sessions do not hit the storage driver until your code calls req.session.get(), saving overhead on static or lean routes.

Write Optimization

Arika tracks mutations; if session data isn't changed during a request, the persist step is skipped entirely.

Concurrency Locking

Native support for Redis and Database locks to prevent "Last-Write-Wins" race conditions in concurrent requests.

Flash Memory

Built-in one-request persistence perfect for status alerts, managed automatically by the lifecycle.

Configuration

Configure your drivers and lifecycle settings in config/session.ts. Arika supports file, redis, database, and memory drivers out of the box.

config/session.ts
export default {
    driver: 'redis',
    lifetime: 120, // Minutes
    locking: true,
    lockTimeout: 10, // Seconds
    secret: env('APP_KEY')
};

The Session API

Controller Interaction
// 1. Storing Data
await req.session.set('theme', 'dark');
await req.session.put('cart:total', 450);

// 2. Retrieving Data
const theme = await req.session.get('theme', 'light');
const allData = await req.session.all();

// 3. Checking & Removing
if (await req.session.has('theme')) {
    await req.session.forget('theme');
}

// 4. Flash Data (One-Request Persistence)
await req.session.flash('status', 'Welcome back!');
await req.session.reflash(); // Persist for another hop

// 5. Security & Lifecycle
await req.session.regenerate(); // Rotate ID after Login
const csrf = await req.session.token();
await req.session.regenerateToken();

// 6. Destructing
await req.session.destroy();

Security & Governance

Preventing session fixation and tampering is critical for production applications. ArikaJS includes built-in methods for session rotation and cryptographically signed cookies.

HMAC-SHA256 Signing

Every session ID cookie is signed with your application secret. If the payload is tampered with, Arika discards the cookie automatically.

Timing-Safe Verification

Arika uses constant-time comparison algorithms to verify signatures, protecting your app against side-channel timing attacks.

Concurrency Locking

When multiple requests for the same session are sent concurrently, Arika can use a mutex lock to ensure that the session data is updated sequentially. This is highly recommended for Redis or Database backends to prevent state corruption during complex user flows.

The Basics

Validation

ArikaJS provides a unified, declarative validation engine that bridges the gap between raw HTTP input and your application logic. It supports automatic error flashing, type-safe FormRequests, and deep-nested attribute validation.

FormRequest Architecture

Encapsulate rules and authorization logic into dedicated, injectable classes for clean controllers.

Nested Data & Wildcards

Validate complex JSON payloads using dot notation (user.email) and array wildcards (posts.*.title).

Auto-Error Persistence

Failed validations automatically redirect back with errors and old input flashed to the session.

Custom Validation

Extend the core validator with your own business logic using the Rule contract.

Ad-hoc Validation

For simple scenarios, use the req.validate() method directly within your controller. If validation fails, Arika automatically throws a ValidationError which is handled by the global exception handler.

app/Http/Controllers/PostController.ts
public async store(req: Request) {
    const data = await req.validate({
        title: 'required|string|max:255',
        'author.email': 'required|email',
        'tags.*': 'alpha_num'
    });

    // data contains only validated keys...
}

FormRequests (Deep Architecture)

For complex logic, create a FormRequest. This class separates validation and authorization from your controller, making your code easier to test and maintain.

app/Http/Requests/StoreUserRequest.ts
import { FormRequest } from 'arikajs';

export class StoreUserRequest extends FormRequest {
    public authorize(): boolean {
        return this.auth.user().isAdmin();
    }

    public rules(): Record<string, any> {
        return {
            name: 'required|min:3',
            email: 'required|email|unique:users,email',
            password: 'required|confirmed|min:8'
        };
    }
}

Custom Validation Rules

Register custom rules to handle unique domain logic. Rules implement the Rule contract and can perform asynchronous checks (like database lookups).

app/Rules/Uppercase.ts
export class Uppercase implements Rule {
    async validate(value: any): Promise<boolean> {
        return typeof value === 'string' && value === value.toUpperCase();
    }

    message(): string {
        return 'The :attribute must be uppercase.';
    }
}

Available Validation Rules

ArikaJS includes a massive library of built-in validation rules to handle every data scenario. Below is a comprehensive reference of all available rules.

required

The field under validation must be present in the input data and not be empty (null, undefined, or empty string).

nullable

The field can be null or empty. If it is null, validation for other rules on this field will be skipped.

sometimes

The field will only be validated if it is actually present in the input data. Useful for optional updates.

bail

Stop running validation rules for the field after the first validation failure is encountered.

email

The field must be formatted as a valid email address.

min:value

For strings, length must be >= value. For numbers, value must be >= value. For arrays, count must be >= value.

max:value

For strings, length must be <= value. For numbers, value must be <=value. For arrays, count must be <=value.

string

The field under validation must be a valid JavaScript string.

number

The field under validation must be numeric (integer or float).

boolean

The field must be a boolean (true/false) or a boolean-equivalent like "1", "0", "true", "false".

array

The field under validation must be a valid JavaScript array.

alpha

The field must be entirely alphabetic characters (A-Z, a-z).

alpha_num

The field must be entirely alpha-numeric characters (A-Z, a-z, 0-9).

url

The field must be a valid URL (including protocol like http/https).

confirmed

The field must have a matching field of {field}_confirmation (e.g., password needs password_confirmation).

in:foo,bar

The field must be one of the values listed in the comma-separated parameters.

not_in:foo,bar

The field must NOT be in the provided list of values.

required_if:field,val

The field is required only if the other field is equal to the provided val.

The Basics

Error Handling

ArikaJS features a sophisticated, unified exception handling system. By default, all exceptions are routed through a central Handler class that intelligently manages reporting to log drivers and rendering responses based on the request environment.

Pretty Stack Traces

In development mode, Arika renders a beautiful, interactive error page with code previews and syntax highlighting.

Smart Content Discovery

Automatically detects API requests (/api/*) and returns structured JSON errors instead of HTML pages.

Custom Error Views

Easily override default error pages by placing templates in resources/views/errors/{status}.ark.html.

Global Reporting

Configurable dontReport lists to prevent noise from common errors (like 404s) in your log drives.

The Exception Handler

Every ArikaJS application contains an app/Exceptions/Handler.ts class. This is where you configure how different exceptions should be reported and rendered.

app/Exceptions/Handler.ts
import { Handler as BaseHandler } from 'arikajs';
import { Log } from '@arikajs/logging';

export class Handler extends BaseHandler {
    // 1. Prevent certain types from filling up logs
    protected dontReport = [
        InvalidTokenException
    ];

    // 2. Logic for external reporting (Sentry, Bugsnag, etc)
    public report(error: any): void {
        super.report(error);
    }

    // 3. Override rendering for specific needs
    public async render(req: Request, error: any, res: Response) {
        return super.render(req, error, res);
    }
}

Custom Error Pages

To create a custom error page for a specific status code (e.g., 404), simply create a file at resources/views/errors/404.ark.html. ArikaJS will automatically use it for browser requests.

resources/views/errors/404.ark.html
<div class="error-container">
    <h1>Page Not Found</h1>
    <p>The URL you requested could not be found.</p>
</div>
The Basics

Logging

Observability is a first-class citizen in ArikaJS. Built on a powerful, channel-based logging system, it allows you to dispatch logs to multiple destinations across different formats with zero configuration overhead.

Channel-Based

Switch between single, daily, slack, or composite stack channels effortlessly.

Structured JSON

Native support for JSON formatting, making your logs ready for ELK, Datadog, or CloudWatch out of the box.

Shared Context

Maintain state across log entries using Log.withContext(), perfect for request tracing.

Slack Alerts

Built-in driver to pipe critical and emergency errors directly into your team's Slack channels via webhooks.

Basic Usage

App Interaction
import { Log } from 'arikajs';

// Standard logging levels
Log.info('Application started');
Log.error('Payment processing failed', { userId: 123 });

// targeted channel logging
Log.channel('slack').critical('Database is unreachable!');

// Injecting context for a request lifecycle
Log.withContext({ trace_id: 'req_001' }).info('Processing order');

Log Drivers & Configuration

Configure your logging strategy in config/logging.ts. ArikaJS supports multiple drivers including single (file), daily (rotating), slack, and stack.

config/logging.ts
export default {
    default: env('LOG_CHANNEL', 'stack'),
    channels: {
        stack: {
            driver: 'stack',
            channels: ['daily', 'console']
        },
        daily: {
            driver: 'daily',
            path: 'storage/logs/arika.log',
            days: 14,
            formatter: 'json'
        }
    }
}
Digging Deeper

Arika CLI

The Arika CLI (Command Line Interface) is the central nervous system of your development workflow. It automates repetitive tasks—from project scaffolding and database migrations to complex job processing and security management—ensuring you spend more time writing logic and less time on boilerplate.

Zero-Install (npx)

Use npx @arikajs/cli to run any command without cluttering your global node_modules.

Smart Scaffolding

Generators create models, migrations, and controllers simultaneously using short-flags like --mc.

Hot Reloading

Built-in development server with instant HMR for templates and background watchers for business logic.

Env Governance

Native env:validate support to ensure your production environment matches your template requirements.

General Commands

arika new <name>

Scaffolds a complete, ready-to-run ArikaJS application in a new directory.

arika serve --dev

Starts the development server with Hot Module Replacement (HMR).

arika key:generate

Generates a cryptographically secure 32-character APP_KEY for your .env file.

arika env:validate

Checks if your current .env file contains all required keys from .env.example.

Code Generators (make:*)

Speed up development by generating boilerplate classes that follow ArikaJS design patterns.

make:controller <Name>

Generate a new controller class in app/Controllers/.

make:model <Name>

Generate an Active Record model. Use --mc for model, migration, and controller.

make:migration <Name>

Generate a timestamped migration file for versioned database schemas.

make:middleware <Name>

Create a new HTTP middleware class to intercept and handle requests.

make:provider <Name>

Scaffold a Service Provider for registering container bindings.

make:job <Name>

Generate a background job class for asynchronous processing.

Database & Migrations

arika migrate

Runs all pending database migrations to update your schema.

arika migrate:rollback

Reverts the most recent batch of database migrations.

arika migrate:fresh

Drops all tables and re-runs all migrations. Perfect for local dev reset.

arika db:seed

Populates your database tables with dummy or initial data using Seeders.

Queue & Scheduling

arika queue:work

Starts the background worker to process pending jobs in your queue.

arika queue:retry <id>

Retries a specifically failed job from the failed_jobs table.

arika schedule:run

Runs the scheduled tasks that are due (typically called by a Cron job).

arika schedule:work

Starts a persistent worker to process schedules locally without Cron.

Optimization & Auth

arika auth:install

Interactive menu to scaffold complete auth logic. Choose between Session-based Web or JWT-based API flows.

arika socialite:install

Installs OAuth scaffolding for Google, GitHub, Facebook, and X social authentication.

arika route:list

Displays a formatted table of all registered routes, middleware, and names.

arika env:validate

Audits your current .env against .env.example to ensure all required keys are populated.

arika cache:clear

Flushes the application cache and clears all data from the active driver.

arika storage:link

Creates a symbolic link from public/storage to storage/app/public.

Digging Deeper

Cache

ArikaJS provides a unified, driver-based caching system designed for extreme performance. Whether you need the simplicity of local file storage or the power of distributed Redis clusters, the Arika Cache API remains consistent, expressive, and type-safe.

Multi-Driver Architecture

Seamlessly switch between file, redis, database, and memory drivers with a single env change.

Blade Partial Caching

Cache expensive UI fragments directly in your views using the lightning-fast @cache directive.

Atomic Persistence

Guaranteed data integrity across distributed nodes using atomic locks and cross-process mutation protection.

Zero-Config Defaults

Comes pre-configured with a high-performance file driver that requires no external dependencies.

Configuration

Global cache settings are managed in config/cache.ts. This file defines your default store and the specific settings for each available driver.

config/cache.ts
export default {
    // Default store used when Cache.get() is called without a store selection
    default: env('CACHE_STORE', 'file'),

    stores: {
        file: {
            driver: 'file',
            path: 'storage/cache/data',
        },
        redis: {
            driver: 'redis',
            connection: 'default', // Matches config/database.ts redis connections
        },
        database: {
            driver: 'database',
            table: 'cache',
            connection: 'default',
        },
        memory: {
            driver: 'memory',
        }
    }
}

Environment Variables (.env)

Easily pivot your caching strategy between environments (e.g., File for Local, Redis for Production):

.env
CACHE_STORE=redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

Usage in Controllers

Interact with the cache using the Cache facade. All methods are asynchronous and return Promises.

app/Controllers/UserController.ts
import { Cache } from 'arikajs';
import { User } from '../Models/User';

export class UserController {
    public async index() {
        // Fetch from cache or execute DB query if missing
        const users = await Cache.remember('all_users', 3600, async () => {
            return await User.all();
        });

        return view('users.index', { users });
    }
}

Usage in Ark Blade

The @cache directive allows you to cache complete sections of your HTML. This is incredibly powerful for complex navigation menus, sidebar stats, or product lists that don't change frequently.

resources/views/partials/sidebar.ark.html
<!-- Cache this sidebar for 1 hour using a unique key -->
@cache('sidebar_stats', 3600)
    <div class="stats">
        <h4>Total Users: {{ $total_users }}</h4>
        <p>Last compiled at: {{ now() }}</p>
    </div>
@endcache

Supported Drivers

File (Default)

Stores serialized objects in storage/cache/data. Best for simple standalone apps.

Memory

Ultra-fast in-memory Map backend. Data is lost upon server restart. Perfect for testing.

Database

Uses your existing DB connection. Run arika cache:table to generate the required migration.

Redis

Professional-grade distributed caching. Supports tags and complex data types with high-concurrency.

Advanced: Atomic Locks

Prevent race conditions across multiple server instances using distributed locks. Locks are automatically released when the TTL expires or when manually released.

Logic
const lock = Cache.lock('processing-payment', 10);

if (await lock.acquire()) {
    try {
        // This code is guaranteed to run on only ONE server at a time
    } finally {
        await lock.release();
    }
}
Digging Deeper

Events

Arika Events provide a sophisticated observer implementation, allowing you to subscribe and react to various occurrences in your application. By following the "React, Don't Call" philosophy, you ensure your business logic remains decoupled, testable, and highly scalable.

Automatic Injection

Listeners are automatically resolved via the Service Container, allowing you to inject any dependency (e.g., Mailer, DB) into the constructor.

Queued Listeners

Simply add public shouldQueue = true to any listener class to offload the work to the background queue system automagically.

Ordered Execution

Gain precise control over side-effect execution order using numeric priorities—higher priorities run before lower ones.

Wildcard Patterns

React to groups of events using wildcard matching. For example, order.* catches order.created and order.shipped.

Real-World Example: Order Processing

In a typical e-commerce application, placing an order triggers multiple side effects: reducing inventory stock (Synchronous/Immediate) and sending a confirmation email (Asynchronous/Queued). First, generate your components:

Terminal
arika make:event OrderPlaced
arika make:listener UpdateInventory --event=OrderPlaced
arika make:listener SendOrderEmail --event=OrderPlaced

Defining Class-Based Events

An event class is a simple data container for the information associated with the event, usually acting as a DTO (Data Transfer Object).

app/Events/OrderPlaced.ts
import { Order } from 'app/Models/Order';

export class OrderPlaced {
    constructor(public order: Order) {}
}

Implementing Listeners

Listeners contain the logic for the side-effect. They receive the event instance in their handle() method.

app/Listeners/SendOrderEmail.ts
import { OrderPlaced } from 'app/Events/OrderPlaced';
import { Mail } from 'arikajs';

export class SendOrderEmail {
    // Offload to background queue automatically
    public shouldQueue = true;

    async handle(event: OrderPlaced) {
        await Mail.to(event.order.customer.email)
            .send(new OrderConfirmationEmail(event.order));
    }
}

Registration & Dispatching

Register your mappings in the EventServiceProvider and dispatch them using the Event facade from your Controllers.

app/Providers/EventServiceProvider.ts
import { Event } from 'arikajs';
import { OrderPlaced } from 'app/Events/OrderPlaced';
import { UpdateInventory, SendOrderEmail } from 'app/Listeners';

// Within boot() or register()
Event.listen(OrderPlaced, [
    UpdateInventory, // Executes first (Priority 0)
    SendOrderEmail,   // Dispatched to background queue
]);

// --- In your CheckoutController ---
Event.dispatch(new OrderPlaced(newOrder));

Event Subscribers

Subscribers are classes that allow you to subscribe to multiple events from within the same class, keeping related logic grouped together.

Event Subscribers

Subscribers allow you to handle multiple event types from a single class, grouping related event logic together for better organization.

app/Listeners/UserSubscriber.ts
export class UserSubscriber {
    public subscribe(events) {
        // Register multiple mappings at once
        events.listen('auth.login', this.onLogin);
        events.listen('auth.logout', this.onLogout);
    }

    async onLogin(event) { /* Logic... */ }
    async onLogout(event) { /* Logic... */ }
}

Core API Reference

Event.listen(event, listener, priority = 0)

Assign a listener to an event. Use a closure or a class name. Higher priority numbers execute first.

Event.dispatch(instance)

Fire an event instance, triggering all registered sync and queued listeners.

Advanced Patterns

Gain precise control over execution flow using priorities and catch-all listeners.

Priorities & Wildcards
// High priority runs first
Event.listen(OrderPlaced, ProcessPayment, 100);
Event.listen(OrderPlaced, NotifyCustomer, 10);

// Wildcard catch-all
Event.listen('order.*', (event) => {
    // Triggers for order.placed, order.shipped, etc.
    Log.info('Order activity detected', event);
});
Digging Deeper

File Storage

Arika Storage provides a clean, unified API for working with files across local and cloud-based systems. It abstracts complex filesystem interactions into a consistent interface, allowing you to swap storage backends without touching a single line of business logic.

Abstracted Filesystem

A single API for Local disk, Amazon S3, Google Cloud, and Azure Blob Storage.

Stream Support

Efficiently handle massive files using putStream and readStream to optimize memory usage.

Signed URLs

Generate secure, time-limited temporary links for private cloud files using temporaryUrl.

Storage Middleware

Intercept and transform file operations (e.g., automatic PDF compression or image resizing).

Configuration & Drivers

Environment variables in your .env file control your primary storage strategy, while config/storage.ts maps those variables to specific driver instances.

.env / config/storage.ts
// 1. Environment Controls
FILESYSTEM_DISK=s3
AWS_ACCESS_KEY_ID=your-key
AWS_SECRET_ACCESS_KEY=your-secret
AWS_BUCKET=my-app-assets

// 2. Configuration Mapping
export default {
    default: env('FILESYSTEM_DISK', 'local'),

    disks: {
        local: {
            driver: 'local',
            root: 'storage/app',
        },
        s3: {
            driver: 's3',
            key: env('AWS_ACCESS_KEY_ID'),
            secret: env('AWS_SECRET_ACCESS_KEY'),
            bucket: env('AWS_BUCKET'),
            region: env('AWS_DEFAULT_REGION'),
        }
    }
}

Storage API Reference

put(path, data)

Write string or Buffer content to the current disk.

get(path)

Retrieve the raw Buffer content of a specific file.

putStream(path, stream)

Pipe a readable stream directly to storage for memory optimization.

readStream(path)

Open a readable stream from a stored file for efficient delivery.

exists(path)

Determine if a file exists on the target disk (Returns Promise<boolean>).

delete(path)

Remove a file from the disk permanently.

size(path)

Get the file size in total bytes.

mimeType(path)

Detect the file's content type (e.g., image/jpeg).

lastModified(path)

Fetch the UNIX timestamp of when the file was last updated.

url(path)

Generate a public URL prefix for a stored file.

The Public Disk & CLI

By default, files stored in storage/app are not accessible from the web. To make files public, store them in storage/app/public and use the CLI to create a symbolic link.

Terminal
arika storage:link

This command creates a symlink from public/storage to storage/app/public. You can then access public files via https://your-app.com/storage/file.jpg.

Cloud Visibility (S3)

When using cloud drivers like S3, you can specify individual file visibility (public or private) during the upload process. This is useful for assets that should be publicly readable without signed URLs.

Visibility Settings
// Store with public-read ACL
await Storage.disk('s3').put('avatar.jpg', fileContent, {
    visibility: 'public'
});

// Check visibility
const visibility = await Storage.getVisibility('file.jpg');

File Mutations

Arika provides high-level mutation methods that handle the reading, modifying, and writing cycle automatically, even if the underlying driver doesn't support them natively.

Controller Logic
await Storage.copy('source.jpg', 'backup/source.jpg');
await Storage.move('temp.csv', 'archive/2024.csv');

// Atomic content modification
await Storage.append('audit.log', `New entry at \${Date.now()}`);
await Storage.prepend('security.log', 'Critical Header');

Cloud-Ready Signed URLs

When using S3, GCS, or Azure, you can generate time-limited secure links for private files. These links expire automatically after the specified time.

Secure Link Delivery
// Link that expires in 5 minutes
const expiry = new Date(Date.now() + 5 * 60 * 1000);
const secureUrl = await Storage.disk('s3').temporaryUrl('sensitive-report.pdf', expiry);

CLI & Public Accessibility

To serve local storage files over the web, your files must be in storage/app/public and linked to your application's public root.

Terminal
arika storage:link

This command creates a symlink from public/storage to storage/app/public. Once linked, you can generate accessible URLs using: Storage.disk('public').url('path/to/file.png').

Digging Deeper

Localization

Arika Localization provides a convenient way to retrieve strings in various languages, allowing you to easily support multiple languages within your application. It supports dot-notation lookups, pluralization, and dynamic replacements.

JSON-Based Discovery

Organize translations into clean, lightweight JSON files grouped by module or page.

Pluralization Engine

Handle complex singular and plural forms using pipe-delimited ranges like {0} none|{1} one|[2,*] many.

Dot-Notation Access

Navigate deeply nested JSON structures using simple string paths like auth.login.success.

Automatic Fallbacks

Seamlessly fall back to your default language if a translation is missing in the user's current locale.

Language Files

By default, ArikaJS stores translation files in the resources/lang directory. Each subdirectory represents a supported language (e.g., en, es, fr).

resources/lang/en/messages.json
{
    "welcome": "Welcome to our application!",
    "greeting": "Hello, :name"
}

Basic Usage & Helpers

ArikaJS provides several global helpers for retrieving translations, with lang() being the primary method. You can also pass an object of replacements for dynamic content.

app/Controllers/HomeController.ts
import { lang } from 'arikajs';

export class HomeController {
    public async index() {
        // Simple lookup
        const welcome = lang('messages.welcome');
        
        // With placeholders
        const hello = lang('messages.greeting', { name: 'Prakash' });
        
        return view('welcome', { welcome, hello });
    }
}

Advanced Pluralization

Pluralization is handled using the "pipe" (|) character to distinguish between singular and plural forms. You can use explicit ranges like {0}, [1,5], or [6,*] for complex logic.

resources/lang/en/cart.json
{
    "items": "{0} Your cart is empty|{1} You have one item|[2,*] You have :count items"
}

Use the choice method on the Translator instance to resolve plural forms:

Controller Usage
import { app, Translator } from 'arikajs';

// Resolve based on the numeric value using the Translator singleton
const translator = app().make(Translator);
const status = translator.choice('cart.items', count);

Using in Ark Blade Templates

The global helpers are shared with all views automatically. You can use standard mustache syntax to render localized strings directly in your HTML.

resources/views/welcome.ark.html
<h1>{{ lang('messages.welcome') }}</h1>
<p>{{ lang('messages.greeting', { name: user.name }) }}</p>
Digging Deeper

Mail

ArikaJS provides a clean, driver-based email delivery system built on top of popular transports. It supports templated emails, attachments, and asynchronous delivery through the Arika Queue system.

Configuration

You can configure your mailers in the config/mail.ts file. Arika supports multiple mailers, allowing you to use different transports for different parts of your application.

config/mail.ts
export default {
    default: env('MAIL_MAILER', 'smtp'),
    mailers: {
        smtp: {
            transport: 'smtp',
            host: env('MAIL_HOST', 'smtp.mailtrap.io'),
            port: env('MAIL_PORT', 587),
            username: env('MAIL_USERNAME'),
            password: env('MAIL_PASSWORD'),
        },
        log: { transport: 'log' }
    },
    from: {
        address: env('MAIL_FROM_ADDRESS', 'hello@arika.dev'),
        name: env('MAIL_FROM_NAME', 'ArikaJS')
    }
};

Environment Settings

Update your .env file with your mail provider credentials:

.env
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=587
MAIL_USERNAME=your_username
MAIL_PASSWORD=your_password
MAIL_FROM_ADDRESS=hello@example.com

Mailables

ArikaJS uses "Mailables" to represent each type of email sent by your application. These classes are stored in app/Mail and contain the logic for building the email.

app/Mail/WelcomeEmail.ts
import { Mailable } from 'arikajs';

export class WelcomeEmail extends Mailable {
    constructor(public user: any) { super(); }

    public build() {
        return this
            .subject('Welcome to Arika!')
            .view('emails.welcome', { user: this.user });
    }
}

Sending & Queuing

You can send emails synchronously using send(), or dispatch them to the background using queue() for better performance.

Usage Example
import { Mail } from 'arikajs';
import { WelcomeEmail } from 'app/Mail/WelcomeEmail';

// Send immediately
await Mail.to(user.email).send(new WelcomeEmail(user));

// Queue for background processing
await Mail.to(user.email).queue(new WelcomeEmail(user));

HTML Ark Templates

ArikaJS allows you to build beautiful, responsive HTML emails using the Ark View engine. Simply place your email templates in the resources/views/emails directory.

resources/views/emails/welcome.ark.html
<div style="font-family: Arial, sans-serif;">
    <h1>Hello {{ user.name }},</h1>
    <p>Welcome to ArikaJS! We're excited to have you on board.</p>
    <a href="{{ url }}" style="background: #6366f1; color: white; padding: 10px 20px; text-decoration: none;">
        Verify Account
    </a>
</div>

Fluent API Reference

The Mail facade provides a fluent interface to specify every aspect of your email before delivery.

to(address)

Set the primary recipient. Accepts a string or an array of strings.

Mail.to('user@example.com')
cc(address) / bcc(address)

Add carbon-copy or blind-carbon-copy recipients.

Mail.to(u).cc('admin@arika.dev').bcc('audit@arika.dev')
subject(text)

Set the email subject line.

Mail.to(u).subject('Order Confirmation #1234')
view(name, data)

Render an HTML template from resources/views.

Mail.to(u).view('emails.invoice', { order })
text(content)

Set the fallback plain-text content of the email.

Mail.to(u).text('Please view this email in an HTML client.')
attach(path, options?)

Attach a file from your local storage.

Mail.to(u).attach('invoices/inv_123.pdf', { as: 'YourInvoice.pdf' })
replyTo(address)

Set the Reply-To header for the email.

Mail.to(u).replyTo('support@arika.dev')
Digging Deeper

Queues

ArikaJS Queues allow you to defer the processing of a time-consuming task, such as sending an email or performing an expensive calculation, until a later time, drastically speeding up web requests.

Configuration

Queue configuration is managed in config/queue.ts. You can define multiple connections, such as sync for local development and redis or database for production environments.

config/queue.ts
export default {
    default: env('QUEUE_CONNECTION', 'sync'),
    connections: {
        sync: { driver: 'sync' },
        database: {
            driver: 'database',
            table: 'jobs',
            queue: 'default',
        },
        redis: {
            driver: 'redis',
            connection: 'default',
            queue: 'default',
        }
    }
};

Generating Jobs

To create a new job, use the make:job CLI command. This will scaffold a new class in the app/Jobs directory.

Terminal
arika make:job ProcessPodcast

Directory Structure

All queue-related files are organized within the app/Jobs directory, keeping your background logic clean and isolated.

Directory Tree
arikajs-app/
├── app/
│   └── Jobs/
│       ├── ProcessPodcast.ts
│       └── SendWelcomeEmail.ts
├── config/
│   └── queue.ts
└── .env

Defining Jobs

A job class should extend BaseJob and implement the handle() method. You can use dependency injection in the constructor to pass data to the job.

app/Jobs/ProcessPodcast.ts
import { BaseJob } from 'arikajs';

export class ProcessPodcast extends BaseJob {
    constructor(public podcast: any) { super(); }

    public async handle() {
        // Expensive logic here...
        await encodePodcast(this.podcast);
    }
}

Dispatching Jobs

You can dispatch jobs from your controllers or services using the Queue facade. Jobs can be delayed or sent to specific queues.

Usage Examples
import { Queue } from 'arikajs';
import { ProcessPodcast } from 'app/Jobs/ProcessPodcast';

// Basic dispatch
await Queue.dispatch(new ProcessPodcast(podcast));

// Delayed dispatch (in seconds)
await Queue.later(60, new ProcessPodcast(podcast));

// Specific queue and connection
await Queue.connection('redis').push(
    new ProcessPodcast(podcast).onQueue('high')
);

Running Workers

ArikaJS includes a CLI command to process jobs from the queue. Use a process manager like PM2 or Supervisor in production.

Terminal
# Process the default queue
arika queue:work

# Process a specific connection and queue
arika queue:work --connection=redis --queue=high

# Setup database queue table
arika queue:table
arika migrate
Queue & Scheduling

Task Scheduling

ArikaJS Task Scheduling provides a clean, fluent interface for managing periodic tasks within your application. Instead of managing complex cron entries on your server, you define your schedule directly in code.

Fluent Definition

Define schedules using human-readable methods like daily(), hourly(), or everyMinute().

Overlap Protection

Prevent a task from starting if its previous instance is still running using the withoutOverlapping() builder.

Cluster Safety

Built-in leader election ensures that scheduled tasks run only on one server in a clustered environment.

Queue Integration

Automatically offload scheduled tasks to your background queue system for high-performance execution.

Generating Commands

Before scheduling a console command, you must first create it. Use the make:command utility to scaffold a new command class.

Terminal
arika make:command SendNewsletter

Directory Structure

Scheduled tasks and custom console commands are organized within the app/Console directory.

Directory Tree
arikajs-app/
├── app/
│   └── Console/
│       ├── Commands/
│       │   └── SendNewsletter.ts
│       └── Kernel.ts  // <-- Schedule defined here
└── package.json

Defining Schedules

All scheduled tasks are defined in your app/Console/Kernel.ts file. The scheduler receives a Schedule instance that you can use to build your task list.

app/Console/Kernel.ts
import { Schedule } from 'arikajs';

export default (schedule: Schedule) => {
    // Run a console command daily
    schedule.command('app:cleanup').daily();

    // Dispatch a job to the queue every hour
    schedule.job(new SyncInventory()).hourly();

    // Execute a closure every 5 minutes
    schedule.call(async () => {
        await DB.table('users').where('active', false).delete();
    }).everyFiveMinutes().name('delete-inactive-users');
};

Real-World Example: Nightly Maintenance

Common maintenance tasks like backing up the database and clearing logs should run during low-traffic periods. Here is how you implement a robust maintenance schedule:

System Maintenance Schedule
schedule.command('db:backup')
    .dailyAt('01:30')
    .timezone('Asia/Kolkata')
    .withoutOverlapping()
    .onFailure(async (err) => {
        await Notification.send('Backup Failed: ' + err.message);
    });

schedule.command('logs:clear --days=30')
    .weekly()
    .mondays()
    .at('03:00');

Frequency Reference

The scheduler provides a wide variety of frequency constraints that you can chain to your tasks for precise timing.

everyMinute()

Run the task every single minute.

hourly()

Run the task every hour at the top of the hour.

daily()

Run the task every day at midnight (00:00).

dailyAt('13:00')

Run the task daily at a specific 24h time.

weekdays()

Limit the task to Monday through Friday.

cron('* * * * *')

Use a custom cron expression for complex timings.

Executing the Scheduler

To start processing tasks, you can either run a persistent daemon or use the standard system cron utility to trigger the scheduler every minute.

Option 1: Persistent Daemon (Recommended)

Run the scheduler as a worker process. This mode is more efficient because it keeps the application in memory, allowing for sub-minute precision if needed.

Terminal
arika schedule:work

Option 2: System Cron

If you prefer using the built-in system crontab, add the following entry to your server's crontab -e configuration:

Crontab
* * * * * cd /path-to-your-project && arika schedule:run >> /dev/null 2>&1
Security

Authentication

ArikaJS provides a robust, multi-guard authentication system that supports both traditional session-based web applications and modern, stateless API architectures using JSON Web Tokens (JWT).

Multi-Guard

Configure different authentication strategies for your Web and API routes independently.

Password Hashing

Automatic, industry-standard password security using Bcrypt or Argon2 algorithms.

Starter Kits

Scaffold full Login, Registration, and Password Reset systems with one CLI command.

JWT Native

Built-in support for short-lived access tokens and rotating refresh tokens for modern APIs.

Authentication Starter Kits

ArikaJS includes first-party scaffolding commands to get you started instantly with a professional authentication UI or API structure.

CLI Scaffolding
# RECOMMENDED: Interactive setup menu
arika auth:install

# WEB: Scaffolds Blade/Inertia views, Controllers, and Migrations
arika auth:install:web

# API: Scaffolds JWT-based endpoints and Controllers
arika auth:install:api

Configuration & Guards

Your authentication logic is governed by Guards. Sessions are usually the choice for web browsers, while JWT is standard for APIs.

config/auth.ts
export default {
    default: 'web',
    guards: {
        web: {
            driver: 'session',
            provider: 'users',
        },
        api: {
            driver: 'jwt',
            provider: 'users',
            secret: process.env.JWT_SECRET,
        }
    },
    providers: {
        users: {
            driver: 'eloquent',
            model: User,
        }
    }
};

Auth API Reference

The auth facade (and request.auth) provides a consistent API regardless of which guard you are using.

auth.attempt(credentials, remember?)

Attempt to log in with the provided credentials. Returns boolean (session) or token object (JWT).

auth.login(user, remember?)

Manually log in an existing user instance into the system.

auth.logout()

Invalidate the current session or revoke the current JWT token.

auth.user()

Retrieve the currently authenticated user instance. Returns null if unauthenticated.

auth.check()

Determine if the current user is authenticated. Returns boolean.

auth.id()

Get the unique identifier (primary key) of the currently authenticated user.

auth.viaRemember()

Check if the user was authenticated via a "remember me" cookie.

auth.guard('name')

Switch to a specific guard instance for a single operation.

Middleware Security

To restrict access, apply the auth middleware. Users who are not authenticated will be automatically redirected to login or receive a 401 Unauthorized response.

Route Protection
// Use default guard
Route.get('/dashboard', handler).middleware(['auth']);

// Specify a guard
Route.get('/api/me', handler).middleware(['auth:api']);

// Only guests can access (e.g., Login page)
Route.get('/login', handler).middleware(['guest']);
Security

Authorization

While authentication identifies who a user is, authorization determines what they are allowed to do. Arika provides a clean, enterprise-grade system using simple Gates, structured Policies, and robust Role-Based Access Control (RBAC).

Gates & Policies

Use Closure-based Gates for simple checks, or class-based Policies to organize logic around models.

Response Customization

Return custom deny messages and error codes instead of plain booleans.

Roles & Permissions

First-class support for hasRole() and hasPermission() checks integrated seamlessly.

Concurrency Safe

Every request receives an isolated AuthorizationContext with automatic result caching.

Directory Structure

Authorization logic is naturally divided between global Gates (usually registered in providers) and resource-specific Policies within the app/Policies directory.

Directory Tree
arikajs-app/
├── app/
│   ├── Policies/
│   │   ├── PostPolicy.ts      // Protects Post model
│   │   └── CommentPolicy.ts   // Protects Comment model
│   └── Providers/
│       └── AuthServiceProvider.ts // Discovers policies & registers Gates

Real-World Example: Advanced RBAC

In real-world applications, authorization checks need to handle complex rules such as super-admin bypasses and specific denial messages. Policies allow you to encapsulate all this logic cleanly.

app/Policies/PostPolicy.ts
import { AuthResponse } from 'arikajs';

export class PostPolicy {
    /**
     * Bypasses all checks for super admins.
     */
    async before(user, ability) {
        if (user.hasRole('super-admin')) {
            return true;
        }
        return null; // Continue to specific check
    }

    async update(user, post) {
        // Return a standard boolean
        return user.id === post.userId;
    }

    async delete(user, post) {
        if (user.id !== post.userId) {
            // Return a detailed AuthResponse
            return AuthResponse.deny('You do not own this post.', 'POST_NOT_OWNED');
        }
        return AuthResponse.allow();
    }
}

Usage in Controllers

You can directly check abilities against models inside your HTTP controllers. Arika will automatically resolve the correct Policy based on the class of the model provided.

app/Http/Controllers/PostController.ts
export class PostController {
    async update(req: Request, res: Response) {
        const post = await Post.find(req.param('id'));

        // The authorize proxy automatically throws a 403 response
        // if the 'update' ability on the 'post' fails.
        await req.authorize('update', post);

        // Or handle the logic manually if preferred
        if (await req.cannot('delete', post)) {
            return res.json({ error: 'Unauthorized' }, 403);
        }

        // Proceed to update logic...
    }
}

Usage in Views

Arika’s templating engine provides intuitive directives to show or hide portions of your UI conditionally based on user permissions or roles.

resources/views/posts/show.ark.html
<!-- Policy check directly in the template -->
@can('update', post)
    <a href="/posts/{{ post.id }}/edit" class="btn">Edit Article</a>
@else
    <span class="text-muted">Read Only Context</span>
@endcan

<!-- Role check -->
@role('admin')
    <button class="btn btn-danger">Force Delete</button>
@endrole

API Reference

The AuthorizationContext handles requests behind the scenes. It exposes the following methods, available on the req object.

req.can(ability, ...args)

Returns a boolean indicating if the authenticated user is allowed to perform the given ability.

req.cannot(ability, ...args)

The inverse of `can()`. Returns true if the user is denied access.

req.authorize(ability, ...args)

Throws an `AuthorizationException` (403 Forbidden) immediately if the ability fails.

req.any(abilities, ...args)

Returns true if the user can perform ANY of the multiple abilities provided in the array.

req.every(abilities, ...args)

Returns true only if the user can perform ALL of the abilities.

req.hasRole(role) / req.hasPermission(permission)

Performs a direct check against the user's role and permission arrays without evaluating a full policy.

Security

Encryption & Security

ArikaJS provides simple, highly secure application-level encryption tools. Instead of wrestling with low-level Node.js crypto modules, Arika provides a clean Encrypter class built on top of modern AES-256-GCM standards.

AES-256-GCM

Secure, modern authenticated encryption that guarantees both confidentiality and integrity of your data.

Auto-Serialization

Easily encrypt complex JavaScript objects and arrays; they are automatically serialized to JSON under the hood.

Expirable Payloads (TTL)

Set time-to-live bounds on encrypted strings, perfect for temporary secure links or API tokens.

Signed Payloads

Generate HMAC-SHA256 authenticated payloads allowing you to transmit data seamlessly without encryption but with full tamper detection.

Configuration

Before using the encryption services, your application must have a secure APP_KEY configured. This key is used to generate the initialization vectors (IV) and authentication tags.

Terminal
# Generate a secure 32-byte Base64 key
arika key:generate

Real-World Example: Secure Links

A common real-world use case involves encrypting sensitive IDs (like a user's ID) to create temporary, tamper-proof download or unsubscribe links in a Controller.

app/Http/Controllers/DownloadController.ts
import { app } from 'arikajs';
import { Encrypter } from '@arikajs/encryption';

export class DownloadController {
    
    // 1. Generate the secure encrypted token
    async generateLink({ request, view }) {
        const user = await request.user();
        const encrypter = app.make<Encrypter>('encrypter');
        
        // Encrypt an object with a 5-minute TTL (300 seconds) 
        // and a specific AAD context to prevent payload reuse.
        const token = encrypter.encrypt({ id: user.id, file: 'report.pdf' }, { 
            ttl: 300, 
            context: 'secure-download' 
        });

        // Pass token to the view
        return view.render('downloads.show', { secureToken: token });
    }

    // 2. Verify and decrypt on the download route
    async processDownload({ request, response }) {
        try {
            const token = request.param('token');
            const encrypter = app.make<Encrypter>('encrypter');
            
            // Decrypt ensuring the context matches exactly.
            // DecryptionException is thrown if tampered, expired, or wrong context.
            const payload = encrypter.decrypt(token, { context: 'secure-download' });
            
            return response.download(payload.file);

        } catch (error) {
            return response.status(403).send('Invalid or Expired Link');
        }
    }
}

Usage in Ark Views

When you pass encrypted strings from your controllers to your Ark templates, you can safely pipe them directly into your HTML attributes. Because they are Base64 encoded, they are URL-safe.

resources/views/downloads/show.ark.html
<!-- Generate a secure downloadable link -->
<div class="card">
    <h3>Your secure download is ready</h3>
    <p>This link will expire automatically in 5 minutes.</p>
    
    <!-- Use the pre-encrypted variable directly -->
    <a href="/download/{{ secureToken }}" class="btn btn-primary">
        Download Report Now
    </a>
</div>

Signed Data (Non-Encrypted)

Sometimes you need to transmit data over the wire (like sending an email verification link) where the data doesn't need to be kept secret, but it must not be modified by the user.

Signed Payloads
import { app } from 'arikajs';

const encrypter = app.make('encrypter');

// 1. Sign data (Creates an HMAC-SHA256 hash appended to the payload)
const signedToken = encrypter.sign({ email: 'user@example.com' });

// 2. Verify data (Ensures it wasn't tampered with)
// Throws VerificationException if the signature is invalid
const validPayload = encrypter.verify(signedToken);

console.log(validPayload.email); // "user@example.com"

Password Hashing

Unlike encryption, hashing is a one-way process. You never "decrypt" a password; instead, you hash the input and compare it to the stored hash.

Hash Usage
import { Hash } from 'arikajs';

// Securely hash a password (uses Bcrypt or Argon2 internally)
const hashed = await Hash.make(plaintextPassword);

// Check password against a hash
const isValid = await Hash.check(inputPassword, storedHash);
Database & Active Record

Getting Started

Arika makes interacting with databases extremely simple across a variety of supported backends. Currently, Arika supports MySQL, PostgreSQL, and SQLite.

Configuration

Configure your database connections in the config/database.ts file or via .env variables.

Read/Write Separation

Easily use separate database connections for SELECT statements vs INSERT/UPDATE/DELETE.

Prerequisites

Before you begin, ensure you have the appropriate database driver installed for your project. Arika uses knex under the hood for query building and migration management.

Schema & Migrations

Migrations are like version control for your database, allowing your team to easily modify and share the application's database schema. The Schema Builder provides a database-agnostic way of manipulating tables.

Directory Structure

Migrations are stored in the database/migrations directory of your application. Each migration filename contains a timestamp that allows Arika to determine the order of execution.

Structure
database/
└── migrations/
    ├── 2024_01_01_000000_create_users_table.ts
    ├── 2024_01_01_000001_create_posts_table.ts
    └── 2024_01_02_000000_add_votes_to_posts_table.ts

Developing Migrations

Each migration class contains two methods: up and down. The up method is used to add new tables, columns, or indexes to your database, while the down method should reverse the operations performed by the up method.

database/migrations/xxxx_create_posts_table.ts
import { Schema, Blueprint } from '@arikajs/database';

export default class CreatePostsTable {
    async up() {
        await Schema.create('posts', (table: Blueprint) => {
            table.id();
            table.string('title', 255);
            table.text('content').nullable();
            table.boolean('is_published').default(false);
            
            // Foreign Key with fluent constraint
            table.foreignId('user_id')
                 .constrained('users')
                 .onDelete('cascade');

            table.timestamps();
            table.softDeletes(); // adds deleted_at
        });
    }

                                    async down() {
        await Schema.dropIfExists('posts');
    }
}

Column Blueprints

The Blueprint class provides a wide variety of methods for defining your table columns across all supported database systems:

table.id()

Alias for an auto-incrementing BIGINT UNSIGNED primary key.

table.string(name, length)

Equivalent to a VARCHAR column. Length defaults to 255 if not specified.

table.text(name)

Equivalent to a TEXT column for long string content.

table.integer(name)

Equivalent to an INTEGER column.

table.boolean(name)

Equivalent to a BOOLEAN (or tinyint) column.

table.decimal(name, p, s)

Equivalent to a DECIMAL column with precision and scale.

table.json(name)

Equivalent to a JSON column for structured data.

table.uuid(name)

Equivalent to a UUID column.

table.dateTime(name)

Equivalent to a DATETIME column.

table.timestamps()

Adds nullable created_at and updated_at columns.

Column Modifiers

Arika provides fluent "modifier" methods that you may append to columns to further specialize them:

.nullable()

Allows the column to contain NULL values.

.default(value)

Assigns a "default" value to the column.

.unsigned()

Sets INTEGER columns as UNSIGNED (MySQL/PGSQL only).

.after('column')

Places the column "after" another column (MySQL only).

.comment('text')

Adds a database-level comment to the column.

.unique()

Enforces a unique constraint on the column.

Index & Foreign Keys

Define indexes and foreign key constraints fluently. For foreign keys, Arika supports automatic detection of parent tables using the constrained() method.

Constraints
await Schema.table('users', (table) => {
    // Single column index
    table.index('email');

    // Unique index with custom name
    table.unique('username', 'idx_unique_user');

    // Advanced Foreign Key
    table.foreignId('category_id')
         .constrained('categories')
         .onDelete('cascade')
         .onUpdate('restrict');

    // Compound Index
    table.index(['first_name', 'last_name']);
});

CLI Commands

The Arika CLI provides powerful tools for managing your database migrations throughout the development lifecycle.

make:migration

Creates a new migration file with a timestamped prefix in database/migrations.

migrate

Runs all pending migrations that haven't been executed yet.

migrate:rollback

Rolls back the last "batch" of migrations. Use --step=N to rollback multiple batches.

migrate:refresh

Rolls back all of your migrations and then runs the migrate command.

migrate:fresh

Drops all tables from the database and then executes the migrate command.

migrate:status

Displays a table showing which migrations have run and when.

Query Builder

Arika's database query builder provides a convenient, fluent interface to creating and running database queries. It can be used to perform most database operations in your application and works perfectly with MySQL, PostgreSQL, SQLite, and MongoDB.

SQL Injection Protection

Uses automatic parameter binding to protect your application against SQL injection attacks without any extra code.

Memory Efficiency

Process millions of records safely using chunk() or streaming methods which keep RAM usage flat.

Unified Syntax

Write queries once and run them on different database engines without changing a single line of logic.

Type Safe

Built-in TypeScript support ensures your query results match your expected data structures perfectly.

Retrieving Results

The DB facade allows you to start a query on any table. You can chain various constraints as needed before executing the query.

Retrieval Methods
import { DB } from '@arikajs/database';

// 1. Retrieve all records
const users = await DB.table('users').get();

// 2. Retrieve a single record
const user = await DB.table('users').where('id', 1).first();

// 3. Retrieve a single column value
const email = await DB.table('users').where('id', 1).value('email');

// 4. Chunking for large data
await DB.table('users').chunk(1000, async (users) => {
    for (const user of users) {
        // Process 1000 users at a time...
    }
});

Where Clauses

Arika provides a rich set of where clauses to filter your data fluently. You can use operators, arrays, and even subqueries.

Filtering Data
// Basic comparison
await DB.table('users').where('votes', '>', 100).get();

// Logical AND (Implicit)
await DB.table('users')
    .where('active', true)
    .where('role', 'admin')
    .get();

// Logical OR
await DB.table('users')
    .where('votes', '>', 100)
    .orWhere('name', 'John')
    .get();

// Range & Sets
await DB.table('users').whereBetween('age', [18, 30]).get();
await DB.table('users').whereIn('id', [1, 2, 3]).get();
await DB.table('users').whereNull('deleted_at').get();

Joins

Easily association data across multiple tables using standard SQL joins.

Table Joins
// Basic Inner Join
const posts = await DB.table('users')
    .join('posts', 'users.id', '=', 'posts.user_id')
    .select('users.*', 'posts.title')
    .get();

// Left Join
await DB.table('users')
    .leftJoin('posts', 'users.id', '=', 'posts.user_id')
    .get();

Inserts, Updates & Deletes

Modify your data with simple methods that handle SQL generation automatically.

CRUD Operations
// Insert a single record
await DB.table('users').insert({
    name: 'John',
    email: 'john@example.com'
});

// Update multiple records
await DB.table('users')
    .where('id', 1)
    .update({ active: false });

// Increment/Decrement values
await DB.table('posts').increment('views', 1);

// Delete records
await DB.table('users').where('active', false).delete();

Aggregates & Raw SQL

Arika provides high-level methods for common aggregates and supports raw SQL expressions for edge cases.

Advanced Operations
// Aggregate Helpers
const count = await DB.table('users').count();
const maxPrice = await DB.table('orders').max('total');

// Raw SQL Fragments
const results = await DB.table('users')
    .select(DB.raw('count(*) as user_count, status'))
    .groupBy('status')
    .get();

// Transactions
await DB.transaction(async (trx) => {
    await DB.table('accounts').decrement('balance', 100);
    await DB.table('transactions').insert({ amount: 100 });
});

Active Record Models

Arika's Active Record implementation provides a beautiful, object-oriented way to interact with your database. Each database table has a corresponding "Model" that allows you to query, insert, and update records as native JavaScript objects.

Mass Assignment

Protect your data by defining fillable attributes, preventing malicious users from overposting sensitive fields.

Attribute Casting

Automatically convert database types (JSON, Boolean, Date) into native JavaScript types when retrieving records.

Lifecycle Hooks

Tap into model events like creating or deleted to run custom logic globally across your application.

Magic Mutators

Format data on the fly using accessors and mutators (e.g., auto-hashing passwords or formatting names).

Defining Models

Models typically extend the base Model class and reside in app/Models. By default, Arika assumes the table name is the plural form of the class name.

app/Models/User.ts
import { Model } from '@arikajs/database';

export class User extends Model {
    // Explicit table name
    static table = 'users';

    // Attributes allowed for mass-assignment
    protected fillable = ['name', 'email', 'password', 'is_active'];

    // Automatic type casting
    protected casts = {
        is_active: 'boolean',
        settings: 'json',
        last_login: 'datetime'
    };
}

Accessors & Mutators

Accessors and mutators allow you to format model attribute values when you retrieve or set them on a model instance.

Custom Logic
// Mutator: Logic executed when setting the attribute
setPasswordAttribute(value: string) {
    this.attributes['password'] = hash(value);
}

// Accessor: Logic executed when retrieving the attribute
getFullNameAttribute() {
    return `${this.first_name} ${this.last_name}`;
}

// Usage
const user = await User.find(1);
console.log(user.full_name); // Prakash Tank

CRUD Operations

Interaction with models is designed to be fluent and readable, supporting both static and instance-based operations.

Operations
// 1. Creating a record
const user = new User();
user.name = 'John Doe';
await user.save();

// 2. Finding and Updating
const user = await User.find(1);
user.email = 'new@example.com';
await user.save();

// 3. Mass Updates
await User.where('id', 1).update({ name: 'Updated' });

// 4. Deleting
await user.deleteInstance();
await User.delete(1); // Static delete by ID

Query Scopes

Scopes allow you to define common sets of constraints that you may easily re-use throughout your application.

Reusable Logic
class User extends Model {
    static active<T extends Model>(this: typeof Model) {
        return this.query<T>().where('is_active', true);
    }
}

// Usage
const activeUsers = await User.active().get();

Events & Observers

Models fire several events, allowing you to hook into the following points in a model's lifecycle: creating, created, updating, updated, saving, saved, deleting, deleted.

Observers
class UserObserver {
    async creating(user: User) {
        user.role = 'member';
    }

    async created(user: User) {
        await EmailService.sendWelcome(user.email);
    }
}

// Registering the observer
User.observe(new UserObserver());

Relationships

Database tables are often related to one another. Arika makes managing and working with these relationships easy, supporting every major relationship type including Polymorphic and Through associations.

N+1 Prevention

Eliminate performance bottlenecks using Eager Loading (with()) to fetch all related data in a single optimized query.

Pivot Power

Work with many-to-many tables effortlessly using sync(), attach(), and detach() helpers.

Polymorphic Links

A single model can belong to multiple other models on a single association (e.g., Comments for both Posts and Videos).

Dynamic Counting

Retrieve related record counts (like comments_count) without loading real records into memory via withCount().

One To One / Many

The most basic relationships are One-to-One and One-to-Many. These are defined as methods on your model classes.

Basic Associations
// app/Models/User.ts
class User extends Model {
    // One-to-One: A user has one profile
    profile() {
        return this.hasOne(Profile);
    }

    // One-to-Many: A user has many posts
    posts() {
        return this.hasMany(Post);
    }
}

// Usage
const user = await User.find(1);
const posts = await user.posts().get();

Many To Many

Many-to-many relationships require a "pivot" table. For example, a user may have many roles, and those roles are also shared by other users.

Roles & Pivot Tables
// app/Models/User.ts
roles() {
    return this.belongsToMany(Role, 'role_user');
}

// Attaching records
await user.roles().attach(adminRoleId);

// Syncing (Removes old, adds new)
await user.roles().sync([1, 2, 5]);

// Toggling
await user.roles().toggle(editorRoleId);

Polymorphic Relationships

Polymorphic relationships allow a model to belong to more than one other model on a single association.

Morph Logic
// app/Models/Post.ts
comments() {
    return this.morphMany(Comment, 'commentable');
}

// app/Models/Comment.ts
commentable() {
    return this.morphTo();
}

Eager Loading & Performance

To avoid the N+1 query problem, use the with() method to fetch related data upfront.

Optimization
// Execute only 2 queries for any number of users
const users = await User.with('posts').get();

// Nested Eager Loading
const users = await User.with('posts.comments').get();

// Eager load counts only
const authors = await User.withCount('posts').get();
console.log(authors[0].posts_count);

Transactions & Seeding

For enterprise-grade applications, data integrity and testability are paramount. Arika provides robust tools for handling complex database transactions and populating your environment with meaningful test data.

Atomicity

Ensure "all or nothing" execution for complex operations using database transactions with automatic rollback on failure.

Model Factories

Generate thousands of realistic test records in seconds using blueprint-based factories integrated with Faker.

Modular Seeders

Keep your initial data setup clean and organized by splitting logic into separate, reusable seeder classes.

Isolation

Arika handles transaction isolation levels and deadlocks gracefully for high-concurrency environments.

Database Transactions

Transactions allow you to run multiple database operations inside a single "unit of work". If any operation fails, the entire transaction is rolled back.

Transaction Pattern
import { DB } from '@arikajs/database';

// 1. Basic Transaction (Auto-Commit/Rollback)
await DB.transaction(async (trx) => {
    await trx.table('users').decrement('balance', 100);
    await trx.table('ledger').insert({ amount: 100, type: 'debit' });
});

// 2. Dynamic Connection Choice
await DB.connection('mysql').transaction(async (trx) => {
    // Business logic...
});

Model Factories

Factories allow you to define a blueprint for your models so you can quickly generate fake data for testing or development.

database/factories/UserFactory.ts
import { Factory } from '@arikajs/database';
import { User } from 'app/Models/User';

export default class UserFactory extends Factory {
    protected model = User;

    definition() {
        return {
            name: this.faker.name(),
            email: this.faker.email(),
            password: 'password',
            is_active: true
        };
    }
}

Database Seeders

Seeders populate your database with initial data. You can run one specific seeder or a "main" seeder that calls others.

database/seeders/DatabaseSeeder.ts
import { Seeder } from '@arikajs/database';
import { User } from 'app/Models/User';

export default class DatabaseSeeder extends Seeder {
    async run() {
        // 1. Run another seeder
        await this.call([RoleSeeder]);

        // 2. Use Factory to create 50 users
        await User.factory().count(50).create();
    }
}

CLI Commands

Manage your factory and seeding workflow efficiently using the Arika CLI.

make:factory [Model]

Scaffold a new model factory blueprint in your database/factories directory.

make:seeder [Name]

Generate a new seeder class. Ideal for setting up roles, admins, or initial settings.

db:seed

Execute the run method of the main DatabaseSeeder class.

db:seed --class=N

Run a specific seeder class individually without running the entire suite.

Testing & Profiling

ArikaJS treats testing as a first-class citizen, leveraging native Node.js performance to ensure your application remains reliable and blazing fast. By avoiding heavy runner abstractions, Arika provides one of the fastest feedback loops in the ecosystem.

Testing Basics

ArikaJS utilizes the native Node.js test runner (node:test) and node:assert. This eliminates the overhead of heavy libraries like Jest or Mocha, allowing your tests to start instantly with zero configuration.

tests/Unit/MathService.test.ts
import { describe, it } from 'node:test';
import assert from 'node:assert';
import { MathService } from 'app/Services/MathService';

describe('MathService', () => {
    it('can add two numbers correctly', () => {
        const service = new MathService();
        const result = service.add(10, 20);
        
        assert.strictEqual(result, 30);
    });
});

To run your tests, simply use the built-in Node.js runner with tsx for TypeScript support:

# Run all tests in the tests directory
npx tsx --test tests/**/*.test.ts

HTTP Tests

Testing your HTTP layer is straightforward. Arika allows you to boot the application and handle requests through the Kernel without making actual network calls, making integration tests extremely fast and reliable.

tests/Feature/UserApi.test.ts
import { describe, it, before } from 'node:test';
import assert from 'node:assert';
import { createApp, Kernel, Request, Response } from 'arikajs';

describe('User API', () => {
    let app;

    before(async () => {
        app = createApp();
        await app.boot();
    });

    it('returns user profile data', async () => {
        const kernel = app.make(Kernel);
        
        // Mock a request to GET /api/users/1
        const request = new Request(app, { 
            method: 'GET', 
            url: '/api/users/1', 
            headers: {} 
        } as any);
        
        const response = new Response({} as any);

        const result = await kernel.handle(request, response);
        
        assert.strictEqual(result.status(), 200);
        assert.ok(result.getContent().includes('John Doe'));
    });
});

Benchmarking

ArikaJS includes a dedicated performance suite to compare its throughput and latency against other popular frameworks like Express and Fastify. This ensures your application remains at peak performance during development.

To run the benchmark suite, use the Arika CLI:

# Run the default benchmark (GET /hello)
arika benchmark --duration=15 --connections=500
--duration=N

Total seconds to run the benchmark. Recommended 10-30s for stability.

--connections=N

Simultaneous concurrent connections (C10k testing simulations).

--warmup=N

Seconds of traffic to send before recording results to account for V8 JIT optimization.


Arika Socialite

Arika Socialite provides an expressive, fluent interface to OAuth authentication with Google, GitHub, Facebook, X (Twitter), and more. It abstracts the complexity of OAuth 2.0 flows into a unified API.

Integrate social logins into your ArikaJS application in minutes. Socialite handles the redirection, token exchange, and profile normalization, so you can focus on your application logic.

Installation & Setup

The easiest way to get started with Socialite is using the Arika CLI to scaffold the configuration:

Terminal
# Install the package
npm install @arikajs/socialite

# Scaffold configuration and env variables
arika socialite:install

After installation, configure your providers in config/socialite.ts and add the credentials to your .env file:

config/socialite.ts
export default {
    providers: {
        github: {
            client_id: process.env.GITHUB_CLIENT_ID,
            client_secret: process.env.GITHUB_CLIENT_SECRET,
            redirect: `${process.env.APP_URL}/auth/github/callback`,
        },
        google: {
            client_id: process.env.GOOGLE_CLIENT_ID,
            client_secret: process.env.GOOGLE_CLIENT_SECRET,
            redirect: `${process.env.APP_URL}/auth/google/callback`,
            scopes: ['https://www.googleapis.com/auth/userinfo.profile'],
        },
    },
};

Authentication Flow

A typical Socialite flow involves two routes: one for redirecting the user to the provider, and another for receiving the callback after authentication.

app/Controllers/AuthController.ts
import { Socialite } from '@arikajs/socialite';
import { Auth, Request, Response } from 'arikajs';

export default class AuthController {
    /**
     * Redirect the user to the GitHub authentication page.
     */
    async redirectToProvider(req: Request, res: Response) {
        return await Socialite.driver('github').redirect();
    }

    /**
     * Obtain the user information from GitHub.
     */
    async handleProviderCallback(req: Request, res: Response) {
        const socialUser = await Socialite.driver('github').user();

        // socialUser contains: id, nickname, name, email, avatar, etc.
        const user = await User.updateOrCreate(
            { email: socialUser.email },
            { github_id: socialUser.id, avatar: socialUser.avatar }
        );

        await Auth.login(user);
        return res.redirect('/dashboard');
    }
}

Stateless Authentication

If you are building an API or a Single Page Application (SPA) and do not rely on session-based state verification, you can use the stateless method to disable state validation:

Stateless API Usage
const user = await Socialite.driver('google').stateless().user();

Arika Carbon

A fluent, expressive Date & Time library for ArikaJS — inspired by PHP's Carbon. It provides an immutable, chainable, and timezone-aware API that makes date manipulation a joy.

Carbon is bundled with the core framework, meaning you can use it anywhere in your application—from controllers and models to your frontend templates—with zero configuration.

Creating Instances

Carbon provides several static methods to create instances from various formats, including strings, native Date objects, and Unix timestamps.

Carbon Creation
import { Carbon } from 'arikajs';

const now = Carbon.now();                 // Current time
const today = Carbon.today();             // Today at 00:00:00
const dt = Carbon.parse('2025-03-22');     // From ISO string
const fromDate = Carbon.parse(new Date()); // From native Date
const custom = Carbon.create(2025, 6, 15); // Y, M, D

Formatting

Formatting in Carbon is extremely powerful and uses tokens compatible with PHP's date() function.

Date Formatting
const dt = Carbon.parse('2025-03-22 14:30:00');

dt.format('Y-m-d');          // 2025-03-22
dt.format('l, F j, Y');    // Saturday, March 22, 2025
dt.toDateTimeString();      // 2025-03-22 14:30:00
dt.toHumanString();         // March 22, 2025
dt.diffForHumans();          // "2 hours ago" (relative to now)
Y / y

Full 4-digit year / 2-digit year.

F / M / m

Full month name / Short month / Month number.

l / D / d

Full day name / Short day / Day with zeros.

H / i / s

Hours (24h) / Minutes / Seconds.

Manipulation

Every Carbon instance is immutable. Manipulation methods return a new instance, allowing for safe and expressive chaining.

Chaining Scenarios
const dt = Carbon.now()
    .addWeeks(2)
    .subDays(1)
    .startOfDay()
    .addHours(9);

// Check common boundaries
dt.startOfMonth();
dt.endOfYear();
dt.nextSunday();

Comparison

Carbon provides a rich set of boolean helpers for comparing dates quickly in your business logic.

Date Logic
const a = Carbon.parse('2025-01-01');
const b = Carbon.parse('2025-12-31');

a.isBefore(b);       // true
a.isToday();        // false
a.isWeekend();      // depends on the actual day
a.isLeapYear();     // false
a.isBetween(start, end); // boolean

View Integration

The carbon() helper is available globally in every Arika View template, making it incredibly simple to format dynamic data without manual imports.

resources/views/profile.ark.html
<p>Joined: {{ carbon(user.created_at).diffForHumans() }}</p>
<p>Last Update: {{ carbon(user.updated_at).format('d M Y') }}</p>

@if(carbon(post.published_at).isFuture())
    <span class="badge">Scheduled</span>
@endif

Arika Sweet

Premium, advanced alerts and notifications for the ArikaJS ecosystem. Arika Sweet provides a high-performance, glassmorphic UI experience for modern web applications.

Built as a zero-dependency alternative to heavy notification libraries, Sweet offers a fluent API for everything from simple success toasts to complex, multi-step confirmation dialogs and async promise handlers.

Installation

Terminal
# Add the premium alert package
npm install @arikajs/sweet

Basic Usage

Trigger beautiful alerts with a single line of code. Sweet supports shorthand methods for common notification types.

Basic Alerts
import { sweet } from '@arikajs/sweet';

// Simple success alert
sweet.success('Settings saved!');

// Standard fire method
sweet.fire('Confirm Payment', 'Are you ready to proceed?', 'info');

// Stacked Toasts
sweet.toast('Syncing data...', 'info');
sweet.toast('Sync complete!', 'success');

The Promise Handler

Exclusive to the Arika ecosystem, the sweet.promise() method handles the entire lifecycle of an asynchronous task—including loading states, success notifications, and error handling—in a single fluent call.

Async Operations
// Automatically handles UI states for the provided promise
sweet.promise(User.save(data), {
    loading: 'Updating your profile...',
    success: 'Profile updated successfully!',
    error: (err) => `Update failed: ${err.message}`
});

Confirmations

Using async/await, you can create readable confirmation flows that feel native to your code structure.

Logic Flow
if (await sweet.confirm('Danger Zone', 'Delete this project?')) {
    await Project.delete(id);
    sweet.success('Project eradicated.');
}

Arika Docs

Modern, automated, and multi-format documentation engine for the ArikaJS ecosystem. It eliminates the manual work of maintaining API specs by analyzing your routes and generating high-quality artifacts automatically.

Maintain a single source of truth for your API. Arika Docs keeps your OpenAPI specs, Postman collections, and interactive HTML documentation perfectly in sync with your codebase, saving hundreds of hours of manual maintenance.

Getting Started

To begin generating documentation, install the package and use the Arika CLI to scaffold your configuration:

Terminal
# 1. Install the documentation engine
npm install @arikajs/docs

# 2. Scaffold config/docs.ts
arika docs:install

Once installed, register the DocsServiceProvider in your bootstrap/app.ts file to enable the documentation commands:

bootstrap/app.ts
import { DocsServiceProvider } from '@arikajs/docs';

app.register(DocsServiceProvider);

Generating Artifacts

Arika Docs scans your actual route definitions and group structures to build the documentation. To generate your artifacts, run the following command:

Terminal
# Analyze routes and generate HTML, OpenAPI, and Postman files
arika docs:generate

You can customize headings, descriptions, and exclusion patterns in config/docs.ts:

config/docs.ts
export default {
    title: 'Horizon API',
    version: '2.1.0',
    output: {
        html: './public/docs',
        openapi: './docs/specs.yaml',
        postman: './docs/collection.json'
    }
};

Why use Arika Docs?

Interactive Testing

Generate a live Swagger UI that allows developers to test endpoints directly from the browser.

Team Integration

Export Postman collections instantly, empowering your frontend and mobile teams with ready-to-use requests.

Zero Drift

Since docs are generated from actual routes, they never become outdated or "drift" from the implementation.

Arika Deploy

Arika Deploy is a professional, zero-config deployment utility for the ArikaJS ecosystem. It automates the complex lifecycle of process management, reverse-proxy configuration, and SSL termination.

Stop wasting time with manual Nginx configs and PM2 boilerplate. Arika Deploy handles everything from environment health checks to automatic Let's Encrypt SSL setup, allowing you to focus on building features while we handle the infrastructure.

Zero-Config

Automatic detection of your app entry point, port, and package manager.

SSL Automated

Native integration with Certbot for free, auto-renewing Let's Encrypt certificates.

Multi-Server

Full support for both Nginx and Apache reverse-proxy configurations.

Health Checks

Built-in arika doctor command to verify your server environment.

Installation

Arika Deploy should be installed globally on your production server to manage all your ArikaJS applications:

Terminal
npm install -g arika-deploy

Complete Deployment Guide

Follow these 10 steps to take your ArikaJS application from development to a secure, live production environment.

Step 1: Get a Linux Server

Deploy a fresh VPS (Ubuntu 22.04 LTS recommended) from providers like DigitalOcean, Vultr, or AWS.

Step 2: Point Your Domain

Set an A Record in your DNS settings pointing to your server IP. (e.g., @ -> IP and www -> IP).

Step 3: Connect to Server

SSH into your machine: ssh root@your-server-ip

Step 4: Install Node.js
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
Step 5: Install Arika Deploy

Run npm install -g arika-deploy globally.

Step 6: Upload Your App

Clone your repository into /var/www/your-app.

Step 7: Environment Variables

Configure your .env file with production keys and set NODE_ENV=production.

Step 8: Run Doctor

Verify your environment is healthy and all dependencies (PM2, Nginx) are present:

arika doctor
Step 9: Deploy 🚀

Run the deployment command. On the first run, the tool will interactively guide you through the initial configuration. For subsequent deployments, you can use the --yes flag to skip prompts.

# First-time interactive setup
arika deploy

# Subsequent automated deploys
arika deploy --yes
Step 10: Profit!

Your app is now live with HTTPS enabled! Use arika status to monitor processes.

Interactive Setup Questions

When you run arika deploy for the first time, the CLI will ask the following questions to build your configuration:

  • Domain(s): A comma-separated list of domains pointing to this server (e.g., example.com,www.example.com).
  • App Port: The internal port your application listens on (default is 3000).
  • Entry File: The main entry point of your application (e.g., server.js or app.ts).
  • Web Server: Choose between nginx, apache, or none. The tool will auto-detect installed servers.
  • Enable SSL: Whether to automatically provision a Let's Encrypt certificate via Certbot.

Deployment Configuration

All answers from the interactive setup are persisted in a .arika/config.json file at your project root. You can modify this file directly to update your deployment settings.

.arika/config.json
{
  "name": "my-ariak-app",
  "domains": ["example.com", "www.example.com"],
  "port": 3000,
  "server": "nginx",
  "ssl": true,
  "entry": "server.js"
}

Commands Reference

Arika Deploy provides a clean CLI interface for managing your production processes.

Command Description
arika deploy Initialize or update your application deployment.
arika deploy --yes Automatic re-deployment using saved configuration.
arika logs Stream live application logs directly to your terminal.
arika status View detailed status of all running Arika processes.
arika doctor Perform a 7-point health check of the server environment.
arika restart Safely restart application instances with zero downtime.
arika stop Shut down the application processes gracefully.
arika remove Complete teardown of PM2, Nginx, and SSL configs.

Process & Web Server Logic

Arika Deploy automatically generates optimized configuration files for your environment.

PM2 Integration

Applications are managed via PM2 in Cluster Mode (using -i max) to ensure high availability and utilize all available CPU cores. For TypeScript applications, Arika Deploy integrates the tsx interpreter to allow direct execution of source files without manual transpilation steps.

Reverse Proxy

Arika Deploy writes native configuration files to /etc/nginx/sites-available or /etc/apache2/sites-available. It handles proxy headers, web-socket upgrades, and client IP forwarding automatically.

Security Tip:

Always run arika doctor after installing new server-level packages to ensure they are correctly detected by the deployment engine.

Troubleshooting

Deployment can sometimes run into environment-specific issues. Here are the most common solutions:

SSL Failure (DNS Propagation)

If SSL setup fails, ensure your domain's A-record is pointing to the server IP. DNS propagation can take up to 30 minutes. You can retry SSL setup by running arika deploy again.

Port Busy

If the process fails to start because a port is busy, check for existing processes using lsof -i :PORT or change the port in .arika/config.json.

Permission Denied

Writing Nginx/Apache configs requires sudo privileges. If you encounter permission errors, ensure you are running the command as a user with sudo access.

No recent searches