Get Started

Module

Modules are the building blocks of your Antelopejs app. They talk to each other through interfaces - exporting features others can use, and importing features they need.

Managing Modules

Antelopejs modules are self-contained units that implement and expose functionality through interfaces. In this guide, you'll learn how to create and configure modules.

Creating a Module

Making a new module is easy with the CLI:

ajs module init <module-name>

The CLI will ask you to pick a template and choose which interfaces you want to use.

Module Lifecycle

Here's how a module moves through its lifecycle:

The Four Phases

  1. Construct
    • Your module gets its settings
    • You set up resources and connections
    • But nothing is active yet
  2. Start
    • Connections go live
    • Listeners turn on
    • Your module starts working
  3. Stop
    • Pause operations
    • Keep your state intact
    • Ready to start again
  4. Destroy
    • Close all connections
    • Release all resources
    • Clean up completely

When hot reloading happens, the system will destroy the old version and construct the new one, letting you update code without stopping everything.

Always implement all four functions properly to avoid memory leaks and keep things running smoothly during updates.

Module Structure

In Antelopejs, your business logic lives in modules. An Antelopejs project is primarily composed of an antelope.json file and one or more modules that work together. Let's explore how modules are structured and how they connect through the project configuration.

Each module in Antelopejs follows a consistent pattern:

module-name/
├── .antelope/             # Generated automatically by the CLI
│   ├── interface.d        # Contains definition files for interfaces your module imports
├── src/
│   ├── index.ts           # Main entry point
│   ├── interfaces/        # Interfaces this module provides
│   │   └── 1/             # Version 1
│   │       └── index.ts
│   └── implementations/   # Implementations of the interfaces
│   │   └── 1/             # Version 1
│   │       └── index.ts
├── dist/
├── package.json
└── tsconfig.json

Source Files

Let's examine the key files in a module:

src/index.ts

This is the entry point for your module. It exports these important lifecycle functions:

export function construct(config: any): void {
  // Set things up when module is loaded
}

export function start(): void {
  // Start the module's functionality
}

export function stop(): void {
  // Pause operation
}

export function destroy(): void {
  // Clean up resources
}

These functions are called automatically by the Antelopejs runtime at appropriate times.

src/interfaces/

This directory contains the interfaces your module provides to others:

interfaces/
└── payment/           # Interface name
    ├── 1/             # Version 1
    │   └── index.ts   # Interface code
    └── 2/             # Version 2
        └── index.ts   # Updated interface code

Each interface has its own folder with versioned implementations. These interfaces are exposed for other modules to consume. For a detailed explanation of how to design and export interfaces, see the Export Interfaces chapter.

src/implementations/

This is where you implement interfaces that you export in your src/interfaces/ directory:

implementations/
└── database/          # Interface name
    └── dev/           # Version
        └── index.ts   # Your implementation

The implementations directory contains the actual code that fulfills the contract defined by your interfaces. For in-depth information about this pattern and how the proxy system works, refer to the Proxy System chapter.

Configuration Files

Each module has its own configuration files:

antelope.module.json

This file tells Antelopejs about your module configuration. You can configure your module in two ways:

Option 1: Separate configuration file (antelope.module.json)

{
  "imports": ["database@beta", "logger@1"],
  "importsOptional": ["debug-tools@dev"],
  "exportsPath": "dist/interfaces",
  "baseUrl": "dist",
  "paths": {
    "@/*": ["*"]
  },
  "defaultConfig": {
    "port": 3000,
    "debug": false,
    "timeout": 5000
  }
}

Option 2: Configuration in package.json

Alternatively, you can store the same configuration in the antelopeJs key within your package.json:

{
  "name": "payment-module",
  "version": "1.0.0",
  "main": "dist/index.js",
  "antelopeJs": {
    "imports": ["database@beta", "logger@1"],
    "importsOptional": ["debug-tools@dev"],
    "exportsPath": "dist/interfaces",
    "baseUrl": "dist",
    "paths": {
      "@/*": ["*"]
    },
    "defaultConfig": {
      "port": 3000,
      "debug": false,
      "timeout": 5000
    }
  }
}
If both antelope.module.json and the antelopeJs key in package.json are present, the antelope.module.json file will be used.

The configuration options define:

SettingWhat it does
importsLists interfaces you want to use from other modules
importsOptionalLists optional interfaces that won't cause errors if unavailable
exportsPathWhere your interfaces are located
baseUrlWhere module files are located
pathsPath mappings for your code (like in TypeScript)
defaultConfigDefault configuration values used when this module is added to a project

Import Formats

Imports can be specified in two formats:

Simple string format:

{
  "imports": ["api@beta", "redis@beta"]
}

Object format with additional options:

{
  "imports": [
    "api@beta",
    {
      "name": "redis@beta",
      "git": "https://github.com/custom/interfaces.git",
      "skipInstall": true
    }
  ]
}

The object format supports these fields:

FieldDescription
nameInterface name and version (required)
gitCustom git repository URL for the interface
skipInstallSkip automatic installation of interface files

Optional Imports

Optional imports are interfaces your module can use if available, but won't cause startup errors if missing. This is useful for:

  • Development-only interfaces (like debugging tools)
  • Feature enhancements that aren't core to your module
  • Interfaces that might not be available in all environments

tsconfig.json

Standard TypeScript configuration for your module:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "baseUrl": "./src",
    "paths": {
      "@ajs.local/*": ["interfaces/*"],
      "@ajs/*": ["../.antelope/interfaces.d/*"],
      "@/*": ["*"]
    }
  },
  "include": ["src/**/*"]
}

The paths mappings in your tsconfig.json serve important purposes:

  • @ajs.local/*: Maps to your module's own interfaces, allowing you to import interfaces your module exports
  • @ajs/*: Maps to interfaces imported from other modules (in .antelope/interfaces.d/)
  • @/*: A shorthand for importing files from your source directory

Importing Interfaces

To use features from other modules, you need to import their interfaces. Here's how:

Step 1: Tell Your Module What to Import

Add the interface to your module configuration. You can do this in either your antelope.module.json file or in the antelopeJs section of your package.json:

Using antelope.module.json:

{
  "imports": ["api@beta"]
}

Using package.json:

{
  "antelopeJs": {
    "imports": ["api@beta"]
  }
}

You can also manage imports with these commands:

#### Add an interface import
ajs module imports add api@beta

#### List current imports
ajs module imports list

#### Remove an interface import
ajs module imports remove api@beta

For more CLI commands, check out the Module CLI guide.

Step 2: Use It in Your Code

Once configured, you can use the interface in your code like this:

// Import what you need from the API interface
import { Controller, Get, Post, HTTPResult, Parameter } from "@ajs/api/beta";

// Create a controller for users
class UsersController extends Controller("/users") {
  // GET /users - List all users
  @Get("/")
  async listUsers() {
    return {
      users: [
        { id: "user1", name: "John Doe" },
        { id: "user2", name: "Jane Smith" },
      ],
    };
  }

  // GET /users/:id - Get a specific user
  @Get(":id")
  async getUser(@Parameter("id") userId: string) {
    // Return user data (simplified example)
    return { id: userId, name: "John Doe" };
  }

  // POST /users - Create a new user
  @Post("/")
  async createUser() {
    // Return created status with new resource
    return new HTTPResult(201, { id: "new-user", name: "New User" });
  }
}

Typical Module Development Workflow

When developing a module:

  1. Define interfaces in src/interfaces/ if your module provides functionality to others
  2. Create implementations in src/implementations/ for interfaces your module export
  3. Write your core business logic in src/index.ts and additional files
  4. Build your module with TypeScript (tsc or your preferred build tool)
  5. Configure it in your project's antelope.json (see the Project chapter for details)