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.
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:
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:
// 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.
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.
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.
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.
Access deeply nested values effortlessly using expressive dot syntax
like app.timezone.
Values are mapped directly from .env files, allowing
different settings for Local, Staging, and Production.
Use arika config:cache in production to flatten all
configs into a single high-performance file.
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.
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.
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.
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.
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):
# 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.
# 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:
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;
}
}
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:
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:
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:
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.
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.
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.
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
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
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 });
});
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:
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:
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:
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:
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:
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.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.
// 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:
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:
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`).
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:
// 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:
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:
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.
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:
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:
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:
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:
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:
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:
// 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:
// 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:
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:
// 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:
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:
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:
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:
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:
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:
import { createReadStream } from 'fs';
Route.get('/video', (req, res) => {
const stream = createReadStream('video.mp4');
return res.header('Content-Type', 'video/mp4').stream(stream);
});
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:
<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:
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.
// 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.
// 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.
<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.
// 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.
@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
// 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
<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.
<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.
<body>
@spa // Activates instant navigation across your site
@yield('content')
</body>
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.
Sessions do not hit the storage driver until your code calls
req.session.get(), saving overhead on static or lean routes.
Arika tracks mutations; if session data isn't changed during a request, the persist step is skipped entirely.
Native support for Redis and Database locks to prevent "Last-Write-Wins" race conditions in concurrent requests.
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.
export default {
driver: 'redis',
lifetime: 120, // Minutes
locking: true,
lockTimeout: 10, // Seconds
secret: env('APP_KEY')
};
The Session API
// 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.
Every session ID cookie is signed with your application secret. If the payload is tampered with, Arika discards the cookie automatically.
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.
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.
Encapsulate rules and authorization logic into dedicated, injectable classes for clean controllers.
Validate complex JSON payloads using dot notation
(user.email) and array wildcards (posts.*.title).
Failed validations automatically redirect back with errors
and old input flashed to the session.
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.
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.
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).
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.
The field under validation must be present in the input data and not be empty (null, undefined, or empty string).
The field can be null or empty. If it is null, validation for other rules on this field will be skipped.
The field will only be validated if it is actually present in the input data. Useful for optional updates.
Stop running validation rules for the field after the first validation failure is encountered.
The field must be formatted as a valid email address.
For strings, length must be >= value. For numbers, value must be >= value. For arrays, count must be >= value.
For strings, length must be <= value. For numbers, value must be <=value. For arrays, count must be <=value.
The field under validation must be a valid JavaScript string.
The field under validation must be numeric (integer or float).
The field must be a boolean (true/false) or a boolean-equivalent like "1", "0", "true", "false".
The field under validation must be a valid JavaScript array.
The field must be entirely alphabetic characters (A-Z, a-z).
The field must be entirely alpha-numeric characters (A-Z, a-z, 0-9).
The field must be a valid URL (including protocol like http/https).
The field must have a matching field of
{field}_confirmation (e.g., password needs password_confirmation).
The field must be one of the values listed in the comma-separated parameters.
The field must NOT be in the provided list of values.
The field is required only if the other field is equal to
the provided val.
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.
In development mode, Arika renders a beautiful, interactive error page with code previews and syntax highlighting.
Automatically detects API requests (/api/*) and returns
structured JSON errors instead of HTML pages.
Easily override default error pages by placing templates in
resources/views/errors/{status}.ark.html.
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.
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.
<div class="error-container">
<h1>Page Not Found</h1>
<p>The URL you requested could not be found.</p>
</div>
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.
Switch between single, daily,
slack, or composite stack channels effortlessly.
Native support for JSON formatting, making your logs ready for ELK, Datadog, or CloudWatch out of the box.
Maintain state across log entries using Log.withContext(),
perfect for request tracing.
Built-in driver to pipe critical and emergency errors directly into your team's Slack channels via webhooks.
Basic Usage
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.
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'
}
}
}
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.
Use npx @arikajs/cli to run any command without
cluttering your global node_modules.
Generators create models, migrations, and controllers
simultaneously using short-flags like --mc.
Built-in development server with instant HMR for templates and background watchers for business logic.
Native env:validate support to ensure your production
environment matches your template requirements.
General Commands
Scaffolds a complete, ready-to-run ArikaJS application in a new directory.
Starts the development server with Hot Module Replacement (HMR).
Generates a cryptographically secure 32-character APP_KEY for your .env file.
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.
Generate a new controller class in app/Controllers/.
Generate an Active Record model. Use --mc for model,
migration, and controller.
Generate a timestamped migration file for versioned database schemas.
Create a new HTTP middleware class to intercept and handle requests.
Scaffold a Service Provider for registering container bindings.
Generate a background job class for asynchronous processing.
Database & Migrations
Runs all pending database migrations to update your schema.
Reverts the most recent batch of database migrations.
Drops all tables and re-runs all migrations. Perfect for local dev reset.
Populates your database tables with dummy or initial data using Seeders.
Queue & Scheduling
Starts the background worker to process pending jobs in your queue.
Retries a specifically failed job from the failed_jobs
table.
Runs the scheduled tasks that are due (typically called by a Cron job).
Starts a persistent worker to process schedules locally without Cron.
Optimization & Auth
Interactive menu to scaffold complete auth logic. Choose between
Session-based Web or JWT-based API flows.
Installs OAuth scaffolding for Google, GitHub, Facebook, and X social authentication.
Displays a formatted table of all registered routes, middleware, and names.
Audits your current .env against .env.example to ensure all required keys are populated.
Flushes the application cache and clears all data from the active driver.
Creates a symbolic link from public/storage to
storage/app/public.
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.
Seamlessly switch between file,
redis, database, and memory drivers with a
single env change.
Cache expensive UI fragments directly in your views using the
lightning-fast @cache directive.
Guaranteed data integrity across distributed nodes using atomic locks and cross-process mutation protection.
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.
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):
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.
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.
<!-- 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
Stores serialized objects in storage/cache/data.
Best for simple standalone apps.
Ultra-fast in-memory Map backend. Data is lost
upon server restart. Perfect for testing.
Uses your existing DB connection. Run
arika cache:table to generate the required migration.
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.
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();
}
}
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.
Listeners are automatically resolved via the Service Container, allowing you to inject any dependency (e.g., Mailer, DB) into the constructor.
Simply add public shouldQueue = true to any
listener class to offload the work to the background queue system automagically.
Gain precise control over side-effect execution order using numeric priorities—higher priorities run before lower ones.
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:
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).
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.
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.
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.
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.
// 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);
});
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.
A single API for Local disk, Amazon S3, Google Cloud, and Azure Blob Storage.
Efficiently handle massive files using
putStream and readStream to optimize memory usage.
Generate secure, time-limited temporary links for
private cloud files using temporaryUrl.
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.
// 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
Write string or Buffer content to the current disk.
Retrieve the raw Buffer content of a specific file.
Pipe a readable stream directly to storage for memory optimization.
Open a readable stream from a stored file for efficient delivery.
Determine if a file exists on the target disk (Returns Promise<boolean>).
Remove a file from the disk permanently.
Get the file size in total bytes.
Detect the file's content type (e.g., image/jpeg).
Fetch the UNIX timestamp of when the file was last updated.
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.
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.
// 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.
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.
// 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.
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').
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.
Organize translations into clean, lightweight JSON files grouped by module or page.
Handle complex singular and plural forms using
pipe-delimited ranges like {0} none|{1} one|[2,*] many.
Navigate deeply nested JSON structures using simple
string paths like auth.login.success.
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).
{
"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.
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.
{
"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:
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.
<h1>{{ lang('messages.welcome') }}</h1>
<p>{{ lang('messages.greeting', { name: user.name }) }}</p>
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.
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:
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.
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.
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.
<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')
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.
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.
arika make:job ProcessPodcast
Directory Structure
All queue-related files are organized within the app/Jobs
directory, keeping your background logic clean and isolated.
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.
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.
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.
# 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
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.
Define schedules using human-readable methods like
daily(), hourly(), or everyMinute().
Prevent a task from starting if its previous instance
is still running using the withoutOverlapping() builder.
Built-in leader election ensures that scheduled tasks run only on one server in a clustered environment.
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.
arika make:command SendNewsletter
Directory Structure
Scheduled tasks and custom console commands are organized within the
app/Console directory.
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.
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:
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.
Run the task every single minute.
Run the task every hour at the top of the hour.
Run the task every day at midnight (00:00).
Run the task daily at a specific 24h time.
Limit the task to Monday through Friday.
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.
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:
* * * * * cd /path-to-your-project && arika schedule:run >> /dev/null 2>&1
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).
Configure different authentication strategies for your Web and API routes independently.
Automatic, industry-standard password security using Bcrypt or Argon2 algorithms.
Scaffold full Login, Registration, and Password Reset systems with one CLI command.
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.
# 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.
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.
// 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']);
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.
Secure, modern authenticated encryption that guarantees both confidentiality and integrity of your data.
Easily encrypt complex JavaScript objects and arrays; they are automatically serialized to JSON under the hood.
Set time-to-live bounds on encrypted strings, perfect for temporary secure links or API tokens.
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.
# 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.
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.
<!-- 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.
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.
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);
Getting Started
Arika makes interacting with databases extremely simple across a variety of supported backends. Currently, Arika supports MySQL, PostgreSQL, and SQLite.
Configure your database connections in the
config/database.ts file or via .env variables.
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.
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.
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:
Alias for an auto-incrementing
BIGINT UNSIGNED primary key.
Equivalent to a VARCHAR column. Length
defaults to 255 if not specified.
Equivalent to a TEXT column for long
string content.
Equivalent to an INTEGER column.
Equivalent to a BOOLEAN (or tinyint)
column.
Equivalent to a DECIMAL column with
precision and scale.
Equivalent to a JSON column for
structured data.
Equivalent to a UUID column.
Equivalent to a DATETIME column.
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:
Allows the column to contain NULL values.
Assigns a "default" value to the column.
Sets INTEGER columns as UNSIGNED
(MySQL/PGSQL only).
Places the column "after" another column (MySQL only).
Adds a database-level comment to the column.
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.
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.
Creates a new migration file with a timestamped
prefix in database/migrations.
Runs all pending migrations that haven't been executed yet.
Rolls back the last "batch" of migrations. Use
--step=N to rollback multiple batches.
Rolls back all of your migrations and then runs the
migrate command.
Drops all tables from the database and then
executes the migrate command.
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.
Uses automatic parameter binding to protect your application against SQL injection attacks without any extra code.
Process millions of records safely using
chunk() or streaming methods which keep RAM usage flat.
Write queries once and run them on different database engines without changing a single line of logic.
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.
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.
// 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.
// 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.
// 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.
// 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.
Protect your data by defining
fillable attributes, preventing malicious users from
overposting sensitive fields.
Automatically convert database types (JSON, Boolean, Date) into native JavaScript types when retrieving records.
Tap into model events like
creating or deleted to run custom logic
globally across your application.
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.
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.
// 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.
// 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.
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.
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.
Eliminate performance bottlenecks using Eager
Loading (with()) to fetch all related data in a single
optimized query.
Work with many-to-many tables effortlessly
using sync(), attach(), and
detach() helpers.
A single model can belong to multiple other models on a single association (e.g., Comments for both Posts and Videos).
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.
// 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.
// 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.
// 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.
// 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.
Ensure "all or nothing" execution for complex operations using database transactions with automatic rollback on failure.
Generate thousands of realistic test records in seconds using blueprint-based factories integrated with Faker.
Keep your initial data setup clean and organized by splitting logic into separate, reusable seeder classes.
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.
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.
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.
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.
Scaffold a new model factory blueprint in
your database/factories directory.
Generate a new seeder class. Ideal for setting up roles, admins, or initial settings.
Execute the run method of the
main DatabaseSeeder class.
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.
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.
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
Total seconds to run the benchmark. Recommended 10-30s for stability.
Simultaneous concurrent connections (C10k testing simulations).
Seconds of traffic to send before recording results to account for V8 JIT optimization.
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.
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.
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)
Full 4-digit year / 2-digit year.
Full month name / Short month / Month number.
Full day name / Short day / Day with zeros.
Hours (24h) / Minutes / Seconds.
Manipulation
Every Carbon instance is immutable. Manipulation methods return a new instance, allowing for safe and expressive chaining.
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.
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.
<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
# 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.
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.
// 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.
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:
# 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:
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:
# Analyze routes and generate HTML, OpenAPI, and Postman files
arika docs:generate
You can customize headings, descriptions, and exclusion patterns in
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?
Generate a live Swagger UI that allows developers to test endpoints directly from the browser.
Export Postman collections instantly, empowering your frontend and mobile teams with ready-to-use requests.
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.
Automatic detection of your app entry point, port, and package manager.
Native integration with Certbot for free, auto-renewing Let's Encrypt certificates.
Full support for both Nginx and Apache reverse-proxy configurations.
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:
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.
Deploy a fresh VPS (Ubuntu 22.04 LTS recommended) from providers like DigitalOcean, Vultr, or AWS.
Set an A Record in your DNS settings pointing to your server IP. (e.g.,
@ -> IP and www -> IP).
SSH into your machine: ssh root@your-server-ip
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
Run npm install -g arika-deploy globally.
Clone your repository into /var/www/your-app.
Configure your .env file with production keys and set
NODE_ENV=production.
Verify your environment is healthy and all dependencies (PM2, Nginx) are present:
arika doctor
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
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.jsorapp.ts). - Web Server: Choose between
nginx,apache, ornone. 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.
{
"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.
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:
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.
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.
Writing Nginx/Apache configs requires sudo privileges. If you encounter permission errors,
ensure you are running the command as a user with sudo access.
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:
After installation, configure your providers in
config/socialite.tsand add the credentials to your.envfile: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.
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
statelessmethod to disable state validation: