build your MVP in a snap! using the latest and greatest tools in the industry, hono as the backend framework, drizzle orm for database operations and valibot for data transformation
inspired by nestjs & rails, this mini-library should give you MVP in matter of hours not days with complete template or started code for Authentication & Authorization leverageing easy routes setup using minimal decorators for routing and middlwares
- Introduction
- Getting Started
- Project Structure
- Routing
- Controllers
- Models
- Services
- Middlewares
- Database Support
- Module Generation
This project is a TypeScript-based backend framework that provides a structured approach to building web applications. It includes features such as routing, authentication, and database integration.
- Clone the repository
- Install dependencies:
npm install
- Set up your environment variables including database type (postgresql on the main branch, Sqlite or MySql branch of your prefered database of choice) connection details see .env.example
- Run the project:
npm run dev
The project follows a modular structure:
├── modules/
│ ├── Auth/
│ ├── User/
│ ├── Note/
│ ├── Todo/
│ └── ...
├── middlewares/
├── utils/
├── db/
├── routes.ts
└── index.ts
you must follow folder structure for service resolving mechanism, also modules components (controllers, models, services) should be postfixed after the module name (for example if you have todo it should be in folder modules/Todo with components like TodoController.ts TodoModel.ts TodoService.ts) or you can quickly generate it with incldued script
node ./genmodule.js Todo
Each module must have a Controller which where you define routes and handlers but Model and Service is optional. Model is where you define your database tables and data transfer objects lastly services which is where business logic should be handled. in order to get your controller registered you should define it in the routes global object with main route path. by default this step will resolve the service automatically and will gather all your routes under the route path and will make CRUD routes automatically
Routes are defined in src/routes.ts using the RouteConfig interface:
const routeConfig: RouteConfig[] = [
{
path: "todos",
controller: TodoController,
},
{
path: "notes",
controller: NoteController,
standardRoutes: true, // by default true
nestedRoutes: [
{
path: "ooo",
controller: GeneralController,
},
],
},
{
path: "users",
controller: UserController,
},
{
path: "auth",
controller: AuthController,
standardRoutes: false,
},
];
export default routeConfig;
The standardRoutes option automatically generates CRUD routes for a controller its by default true
so its optional but you can disable this feature by setting it to false
. nestedRoutes allows you to define sub-routes for a module.
Controllers handle the request/response cycle. They use decorators to define routes and apply middlewares:
export default class UserController extends BaseController {
static services = [UserService];
constructor(public userService: UserService) {
super(userService);
}
@Post("/login")
@Use(validateBody(LoginSchema)) // multiple middlewares are also possible just supply an array like so @Use([validateBody(LoginSchema), logger()])
async login(c: Context) {
// ...
}
}
To use the controller main service be sure to add the UserService to the services array, as public property in the constructor and in the super method of the constructor. you can add more services in the in the controller but only subsequent services must be added in the services array and as public property in the constructor (not in the super method) like the following:
export default class UserController extends BaseController {
static services = [UserService,NoteService]; // subsequent service here
constructor(public userService: UserService, public noteService:NoteService) { // and here
super(userService); // this is only for the main service of the controller
}
@Post("/login")
@Use(validateBody(LoginSchema)) // multiple middlewares are also possible just supply an array like so @Use([validateBody(LoginSchema), logger()])
async login(c: Context) {
// ...
}
}
Models define the structure of your data and database schemas. They use Drizzle ORM and Valibot for schema definition and validation:
export const authTable = pgTable("auth", {
id: nanoidIdColumn(),
userId: text("user_id").notNull().references(() => userTable.id),
// ...
});
export const LoginSchema = v.object({
email: v.string([v.email()]),
password: v.string([v.minLength(8)]),
});
Services contain the business logic of your application:
export default class AuthService extends BaseService {
async login(email: string, password: string) {
// ...
}
// ...
}
Middlewares can be applied to routes using the @Use decorator:
@Use([middleware1(), middleware2()])
async someRoute(c: Context) {
// ...
}
To be registered in the route boot logger, middlewares should be named functions that return async functions with the same name:
export const authMiddleware = () => {
return async function authMiddleware(c: Context, next: Next) {
// ...
};
};
This project is compatible with MySQL, SQLite, and PostgreSQL. Configure your database connection in the environment variables and be sure to check the branch for each database, check drizzle.config.ts and db/singletonDatabase.ts if you decided to use Sqlite or mysql main branch shows setup for postgresql lastly if you wanna use mysql you need to check the service methods becuase it has different implementation than postgresql or sqlite but the genmodule will work if you change your database dialect in the drizzle.config.ts
You can quickly generate a new module using the provided script:
node ./genmodule.js Todo
This will create a new folder in src/modules/Todo/ with Controller, Model, and Service files.
For more detailed information on each component, please refer to the inline documentation in the source code or raise an issue.