Quickstart
get started with aquinas
Aquinas is a dependency inversion library that uses functions and objects instead of classes and decorators.
Installation
npm install aquinas
Concepts
If you never heard about IoC, DI or Clean Architecture, you might want to check some resources about it first, but here is a quick overview of the workflow we will follow:
-
Create an interface representing some service (e.g.
IUserRepository
). -
Bind that interface to a reference (e.g.
UserRepositoryReference
). -
Create an injectable that implements that reference (e.g.
UserRepository
). -
Create a dock to hold that injectable (e.g.
UserDock
).
Create your first references
Usually, references go into the Domain layer, where your models are defined.
import { reference } from "aquinas";
import type { User } from "./user";
export interface IUserRepository {
createUser(user: User): Promise<void>;
}
export const UserRepositoryReference = reference<IUserRepository>("UserRepository");
import { reference } from "aquinas";
import type { User } from "./user";
export interface IRegisterUserUsecase {
execute(user: User): Promise<User>;
}
export const RegisterUserUsecaseReference = reference<IRegisterUserUsecase>("RegisterUserUsecase");
import { reference } from "aquinas";
/**
* @typedef {Object} User
* @property {string} id
* @property {string} name
* @property {string} email
*/
/**
* @typedef {Object} IUserRepository
* @property {(user: User) => Promise<void>} createUser
*/
export const UserRepositoryReference = reference("UserRepository");
import { reference } from "aquinas";
/**
* @typedef {Object} User
* @property {string} id
* @property {string} name
* @property {string} email
*/
/**
* @typedef {Object} IRegisterUserUsecase
* @property {(user: User) => Promise<User>} execute
*/
/**
* @type {import("aquinas").Reference<IRegisterUserUsecase>}
*/
export const RegisterUserUsecaseReference = reference("RegisterUserUsecase");
Create your first injectables
import { injectable } from "aquinas";
import { UserRepositoryReference } from "./user.i-repository";
export const UserRepository = injectable(UserRepositoryReference)
.implements(() => ({
async createUser(user) {
console.log('Creating user:', user);
},
}));
import { injectable } from "aquinas";
import { RegisterUserUsecaseReference } from "./register-user.i-usecase";
import { UserRepositoryReference } from "./user.i-repository";
export const RegisterUserUsecase = injectable(RegisterUserUsecaseReference)
.deps({
userRepository: UserRepositoryReference,
})
.implements(({ userRepository }) => ({
async execute(user) {
await userRepository.createUser(user);
return user;
}
}));
Register your injectables in a dock
A dock is what holds your injectables and allows you to merge multiple docks together.
import { dock } from "aquinas";
import { UserRepository } from "./core/user.repository";
import { RegisterUserUsecase } from "./core/register-user.usecase";
const UserDock = dock();
UserDock.register(UserRepository, RegisterUserUsecase);
export { UserDock };
Usually you will have multiple docks in your application, one for each module or feature, so you will need to merge them together in your app's main dock.
import { dock } from "aquinas";
import { UserDock } from "./user.dock";
const AppDock = dock();
AppDock.merge(UserDock);
// you can merge more docks here
export { AppDock };
Running your app
Assuming you created controllers or other entrypoints following the same steps, you will just need to resolve them from the AppDock
and start your app.
import { Hono } from 'hono'
import { serve } from '@hono/node-server'
import { AppDock } from "./app.dock";
import { UserControllerReference } from "./domain/user.controller";
const app = new Hono();
const controllers = AppDock.get({
user: UserControllerReference,
});
app.get("/users", controllers.user.get);
serve(app)
Final Structure
This will totally depend on your project structure, but here is a simple example of how your project could look like after following this guide: