arvox-backend
Version:
Un framework backend moderne et modulaire basé sur Hono, TypeScript et l'architecture hexagonale avec authentification Better Auth + Drizzle intégrée
898 lines (714 loc) • 23.5 kB
Markdown
# arvox-backend
Un framework backend TypeScript moderne basé sur Hono, conçu selon l'architecture hexagonale pour créer des APIs robustes et maintenables avec **authentification Better Auth + Drizzle intégrée**.
## ⚡ Installation rapide
Créez un nouveau projet avec le CLI :
```bash
npx create-arvox-app init mon-projet
cd mon-projet
npm run dev
```
Ou pour le développement local :
```bash
# Clone le framework
git clone <repo-url>
cd backend-framework
# Créer un nouveau projet
node bin/create-arvox-app.js init mon-projet
cd mon-projet
# Modifier package.json pour pointer vers le framework local
npm install
npm run dev
```
## 🔐 Authentification Better Auth + Drizzle
Le framework inclut maintenant un **générateur d'authentification intégré** utilisant Better Auth et Drizzle ORM :
### 🚀 Génération rapide
```bash
# Générer l'authentification complète
npx arvox-auth generate --social github,google
# Générer seulement le schéma Drizzle
npx arvox-auth schema --provider postgresql
# Générer seulement la configuration
npx arvox-auth config --social github,discord
# Valider une configuration existante
npx arvox-auth validate
```
### 📁 Fichiers générés dans `./src/db/`
```
db/
├── schema.ts # Schéma Drizzle (users, sessions, accounts, verifications)
├── index.ts # Client de base de données
├── auth.config.ts # Configuration Better Auth
├── integration-example.ts # Exemple d'intégration dans votre app
└── migrations/ # Dossier des migrations
```
### ✨ Schéma généré
```typescript
// db/schema.ts - Tables optimisées pour Better Auth
export const users = pgTable('users', {
id: text('id').primaryKey(),
name: text('name').notNull(),
firstname: text('firstname'),
lastname: text('lastname'),
email: text('email').notNull().unique(),
lastLoginAt: timestamp('last_login_at'),
emailVerified: boolean('email_verified').notNull(),
image: text('image'),
role: text('role').notNull().default('user'),
isAdmin: boolean('is_admin').notNull().default(false),
createdAt: timestamp('created_at').notNull(),
updatedAt: timestamp('updated_at').notNull()
});
export const sessions = pgTable('sessions', {
id: text('id').primaryKey(),
expiresAt: timestamp('expires_at').notNull(),
token: text('token').notNull().unique(),
userId: text('user_id').notNull().references(() => users.id),
impersonatedBy: text('impersonated_by').references(() => users.id),
// ... autres champs
});
```
### 🔧 Intégration dans votre application
```typescript
import { ArvoxFramework, AuthModuleFactory } from 'arvox-backend';
import { db } from './src/db'; // Client généré
import { authConfig } from './src/db/auth.config'; // Configuration générée
// Créer le module d'authentification
const authModule = AuthModuleFactory.create({
auth: authConfig,
db: db,
});
const framework = new ArvoxFramework({
appName: 'Mon API avec Auth',
version: '1.0.0',
port: 3000,
});
// Enregistrer l'authentification
framework.registerModule(authModule.module);
framework.registerService(authModule.authService);
// Routes protégées
const app = framework.getApp();
app.get('/api/protected', authModule.middleware.required, (c) => {
const user = c.get('user');
return c.json({ message: 'Protected endpoint', user });
});
await framework.start();
```
### 🎯 Endpoints d'authentification automatiques
Une fois intégré, votre API dispose automatiquement de :
```
POST /api/v1/auth/sign-up/email # Inscription par email
POST /api/v1/auth/sign-in/email # Connexion par email
GET /api/v1/auth/me # Profil utilisateur
POST /api/v1/auth/sign-out # Déconnexion
GET /api/v1/auth/session # Vérifier la session
# Avec providers sociaux configurés :
GET /api/v1/auth/sign-in/github # Connexion GitHub
GET /api/v1/auth/sign-in/google # Connexion Google
```
### ⚙️ Configuration CLI `arvox-auth`
```bash
# Options disponibles
npx arvox-auth generate \
--provider postgresql \ # ou mysql, sqlite
--output ./custom-db \ # dossier de sortie (défaut: ./src/db)
--auth-url http://localhost:3000 \ # URL de base
--social github,google,discord # providers sociaux
# Commandes spécialisées
npx arvox-auth init # Interface interactive (à venir)
npx arvox-auth schema --provider mysql
npx arvox-auth config --social github
npx arvox-auth validate # Vérifie la configuration
```
## 📦 Installation manuelle
```bash
npm install arvox-backend
# ou
bun add arvox-backend
# ou
pnpm add arvox-backend
```
## 🚀 Simplification des routes
Le framework offre **3 approches** pour créer des routes avec **60-70% moins de code** :
### ✨ Méthode 1 : BaseController simplifié (Recommandé)
```typescript
import { BaseController } from 'arvox-backend';
export class UserController extends BaseController {
constructor() {
super();
this.setupRoutes();
}
private setupRoutes() {
// ✅ AVANT : 40+ lignes de createRoute
// ✅ APRÈS : 8 lignes avec méthodes simplifiées
this.createPostRoute('/users', {
body: z.object({
name: z.string(),
email: z.string().email()
}),
summary: 'Créer un utilisateur'
}, async (c) => {
// Votre logique métier ici
return c.json({ success: true });
});
this.createListRoute('/users', {
summary: 'Liste des utilisateurs',
query: z.object({
page: z.string().optional(),
limit: z.string().optional()
})
}, async (c) => {
// Pagination automatique incluse
return c.json({ users: [] });
});
this.createGetByIdRoute('/users/:id', {
summary: 'Obtenir un utilisateur par ID'
}, async (c) => {
const id = c.req.param('id');
return c.json({ user: { id } });
});
}
}
```
### ⚡ Méthode 2 : Utilitaires Route
```typescript
import { Route } from 'arvox-backend/utils';
// Configuration encore plus simple - style fonctionnel
const userRoutes = [
Route.post('/users', {
body: z.object({
name: z.string(),
email: z.string().email()
}),
summary: 'Créer un utilisateur'
}, async (c) => {
return c.json({ success: true });
}),
Route.getList('/users', {
summary: 'Liste des utilisateurs'
}, async (c) => {
return c.json({ users: [] });
}),
Route.getById('/users/:id', {
summary: 'Utilisateur par ID'
}, async (c) => {
return c.json({ user: { id: c.req.param('id') } });
})
];
```
### � Méthode 3 : Hybride (Best of Both)
```typescript
export class UserController extends BaseController {
constructor() {
super();
this.setupRoutes();
}
private setupRoutes() {
// Mélanger les deux approches selon les besoins
this.createPostRoute('/users', userCreationConfig, this.createUser);
// Ou utiliser les utilitaires directement
this.addRoute(Route.delete('/users/:id', {
summary: 'Supprimer un utilisateur'
}, this.deleteUser));
}
private createUser = async (c) => { /* logic */ };
private deleteUser = async (c) => { /* logic */ };
}
```
## 📊 Comparaison des méthodes
| Critère | Méthode 1 (BaseController) | Méthode 2 (Route Utils) | Méthode 3 (Hybride) |
|---------|---------------------------|------------------------|---------------------|
| **Réduction de code** | ~60% | ~70% | ~65% |
| **Lisibilité** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| **Flexibilité** | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| **Courbe d'apprentissage** | Facile | Très facile | Modérée |
## �🏗️ Architecture
Le framework suit l'architecture hexagonale :
```
src/
├── controllers/ # 🎮 Couche présentation (HTTP)
├── services/ # 💼 Couche application (logique métier)
├── repositories/ # 🗄️ Couche infrastructure (données)
├── use-cases/ # 🎯 Couche domaine (règles métier)
└── types/ # 📝 Types partagés
```
## � Fonctionnalités
- ✅ **Architecture hexagonale** prête à l'emploi
- ✅ **Validation Zod** automatique
- ✅ **Documentation OpenAPI** générée automatiquement
- ✅ **Pagination** intégrée
- ✅ **Gestion d'erreurs** standardisée
- ✅ **CLI de scaffolding** pour nouveaux projets
- ✅ **TypeScript** support complet
- ✅ **Performance** optimisée avec Hono
## �📚 Guide de démarrage
1. **Créer un projet** : `npx create-arvox-app init mon-api`
2. **Choisir votre approche** : BaseController, Route Utils, ou Hybride
3. **Développer** : `npm run dev`
4. **Tester** : Visitez `/health` et `/doc`
5. **Déployer** : `npm run build && npm start`
## 📖 Exemples
Consultez le dossier `examples/` pour voir des implémentations complètes :
- [`simple-api.ts`](examples/simple-api.ts) : API basique avec CRUD
- [`advanced-documentation.ts`](examples/advanced-documentation.ts) : Documentation OpenAPI avancée
## 🔧 CLI Tools
Le framework Arvox fournit **deux CLI** pour faciliter le développement :
### 📦 `create-arvox-app` - Générateur de projets
```bash
# Nouveau projet avec npm
npx create-arvox-app init mon-projet
# Avec bun
npx create-arvox-app init mon-projet --package-manager bun
# Avec pnpm
npx create-arvox-app init mon-projet --package-manager pnpm
```
**Génère :**
- 📁 Structure de dossiers optimisée
- 🏥 Contrôleur Health avec endpoint `/health`
- 📖 Documentation automatique sur `/doc`
- ⚙️ Configuration TypeScript/ESLint
- 🔥 Hot reload avec `tsx`
### 🔐 `arvox-auth` - Générateur d'authentification
```bash
# Génération complète (recommandé)
npx arvox-auth generate --social github,google
# Options avancées
npx arvox-auth generate \
--provider postgresql \
--output ./src/db \
--auth-url http://localhost:3000 \
--social github,google,discord
# Commandes spécialisées
npx arvox-auth schema --provider mysql # Schéma seulement
npx arvox-auth config --social github # Config seulement
npx arvox-auth validate # Validation
```
**Génère :**
- 🗄️ Schéma Drizzle optimisé (users, sessions, accounts, verifications)
- ⚙️ Configuration Better Auth avec providers sociaux
- 📝 Fichier .env.example avec toutes les variables
- 🔗 Client de base de données typé
- 📋 Exemple d'intégration complet
- 🛠️ Scripts de migration Drizzle
### 🎯 Workflow recommandé
```bash
# 1. Créer le projet
npx create-arvox-app init mon-api-auth
cd mon-api-auth
# 2. Générer l'authentification
npx arvox-auth generate --social github,google
# 3. Installer les dépendances auth
npm install better-auth drizzle-orm postgres
# 4. Configurer .env (copier depuis .env.example)
cp .env.example .env
# Éditer .env avec vos vraies valeurs
# 5. Lancer en développement
npm run dev
# 6. Tester les endpoints
curl http://localhost:3000/api/v1/auth/me
curl http://localhost:3000/docs # Documentation
```
---
**🎯 Objectif** : Réduire la verbosité du code de 60-70% tout en gardant la flexibilité et la robustesse.
## 🚀 Fonctionnalités
- **Architecture Hexagonale** : Séparation claire entre Domain, Application et Infrastructure
- **TypeScript First** : Type safety complet avec Zod pour la validation
- **OpenAPI/Swagger** : Documentation API automatique
- **Modulaire** : Système de modules et services extensible
- **Hono Framework** : Performance optimale avec support moderne
- **Classes de Base** : UseCase, Controller, Repository, Service prêts à l'emploi
- **Utilitaires** : Pagination, validation, gestion des réponses standardisées
## 📦 Installation
```bash
npm install arvox-backend
# ou
bun add arvox-backend
```
## 🏗️ Architecture
```
src/
├── domain/ # Couche métier (entités, interfaces, types)
├── application/ # Couche application (cas d'usage, services)
└── infrastructure/ # Couche infrastructure (controllers, DB, APIs externes)
```
## 🚀 Démarrage Rapide
### 1. Configuration de Base
```typescript
import { ArvoxFramework } from 'arvox-backend'
const framework = new ArvoxFramework({
appName: 'Mon API',
version: '1.0.0',
port: 3000,
environment: 'development',
cors: {
origin: '*',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
headers: ['Content-Type', 'Authorization']
},
logging: {
requests: true,
errors: true
}
})
// Démarrer l'application
await framework.start()
```
### 2. Créer un Use Case
```typescript
import { BaseUseCase, ActivityType } from 'arvox-backend'
type CreateUserParams = {
name: string
email: string
}
type CreateUserResponse = {
success: boolean
data?: User
error?: string
}
export class CreateUserUseCase extends BaseUseCase<CreateUserParams, CreateUserResponse> {
constructor(private userRepository: UserRepository) {
super()
}
async execute(params: CreateUserParams): Promise<CreateUserResponse> {
try {
const user = await this.userRepository.create(params)
return this.createSuccessResponse(user)
} catch (error) {
return this.handleError(error)
}
}
log(): ActivityType {
return ActivityType.CREATE_USER
}
}
```
### 3. Créer un Controller
```typescript
import { BaseController, Route } from 'arvox-backend'
import { z } from 'zod'
// Schémas simplifiés
const CreateUserSchema = z.object({
name: z.string().min(1),
email: z.string().email()
})
const UserSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string()
})
export class UserController extends BaseController {
constructor(private createUserUseCase: CreateUserUseCase) {
super()
}
initRoutes() {
// ✨ APPROCHE SIMPLIFIÉE - 70% moins de code !
// POST /users avec createPostRoute (méthode BaseController)
this.createPostRoute(
'/users',
{
request: CreateUserSchema,
response: UserSchema,
summary: 'Create user',
description: 'Create a new user in the system'
},
async (c, body) => {
const result = await this.createUserUseCase.execute(body)
return result.success
? c.json(result, 201)
: c.json(result, 400)
},
{ security: true } // Options : sécurité, multipart, etc.
)
// Alternative avec Route utility
this.controller.openapi(
Route.post('/users-alt', {
request: CreateUserSchema,
response: UserSchema
}, {
tag: 'Users',
summary: 'Create user (alternative)',
security: true
}),
async (c) => {
const body = c.req.valid('json')
const result = await this.createUserUseCase.execute(body)
return result.success
? c.json(result, 201)
: c.json(result, 400)
}
)
// GET /users avec pagination automatique
this.createListRoute(
'/users',
{
response: UserSchema,
summary: 'Get users list',
description: 'Retrieve paginated list of users'
},
async (c, query) => {
// query contient automatiquement : { page, limit, search, sort }
const users = [] // Votre logique ici
const total = 0
return c.json({
success: true,
data: {
items: users,
pagination: {
total, page: query.page, limit: query.limit,
totalPages: Math.ceil(total / query.limit),
hasNext: query.page < Math.ceil(total / query.limit),
hasPrev: query.page > 1
}
}
})
},
{ security: true }
)
// GET /users/{id}
this.createGetByIdRoute(
'/users/{id}',
{
response: UserSchema,
summary: 'Get user by ID'
},
async (c, id) => {
const user = null // Votre logique de récupération
return user
? c.json({ success: true, data: user })
: c.json({ success: false, error: 'User not found' }, 404)
},
{ security: true }
)
// PUT /users/{id}
this.createPutRoute(
'/users/{id}',
{
request: CreateUserSchema.partial(), // Schema de mise à jour
response: UserSchema,
summary: 'Update user'
},
async (c, id, body) => {
const updatedUser = null // Votre logique de mise à jour
return updatedUser
? c.json({ success: true, data: updatedUser })
: c.json({ success: false, error: 'User not found' }, 404)
},
{ security: true }
)
// DELETE /users/{id}
this.createDeleteRoute(
'/users/{id}',
{
summary: 'Delete user'
},
async (c, id) => {
const deleted = true // Votre logique de suppression
return deleted
? c.json({ success: true, data: { deleted: true } })
: c.json({ success: false, error: 'User not found' }, 404)
},
{ security: true }
)
}
}
```
### 4. Créer un Repository
```typescript
import { BaseRepository } from 'arvox-backend'
export class UserRepository extends BaseRepository<User, CreateUserData, UpdateUserData> {
async findById(id: string): Promise<User | null> {
// Implémentation avec votre ORM (Drizzle, Prisma, etc.)
}
async findAll(pagination?: { skip: number; limit: number }): Promise<User[]> {
// Implémentation avec pagination
}
async create(data: CreateUserData): Promise<User> {
// Implémentation de création
}
async update(id: string, data: UpdateUserData): Promise<User> {
// Implémentation de mise à jour
}
async delete(id: string): Promise<boolean> {
// Implémentation de suppression
}
async count(): Promise<number> {
// Implémentation du comptage
}
}
```
### 5. Créer un Module
```typescript
import { IModule } from 'arvox-backend'
import { OpenAPIHono } from '@hono/zod-openapi'
export class UserModule implements IModule {
constructor(
private userController: UserController
) {}
getName(): string {
return 'UserModule'
}
async initialize(): Promise<void> {
console.log('User module initialized')
}
registerRoutes(app: OpenAPIHono): void {
app.route('/api/v1', this.userController.controller)
}
async cleanup(): Promise<void> {
console.log('User module cleaned up')
}
}
```
### 6. Assemblage Final
```typescript
import { ArvoxFramework } from 'arvox-backend'
// Injection de dépendances
const userRepository = new UserRepository()
const createUserUseCase = new CreateUserUseCase(userRepository)
const userController = new UserController(createUserUseCase)
const userModule = new UserModule(userController)
// Configuration du framework
const framework = new ArvoxFramework({
appName: 'Mon API',
version: '1.0.0',
port: 3000
})
// Enregistrement du module
framework.registerModule(userModule)
// Démarrage
await framework.start()
```
## 🛠️ Utilitaires Inclus
### Simplification des Routes
Le framework offre plusieurs approches pour simplifier la création de routes OpenAPI :
#### 🚀 Méthodes BaseController (Recommandé)
```typescript
// Au lieu de 40+ lignes avec createRoute, utilisez :
this.createPostRoute('/users', {
request: CreateUserSchema,
response: UserSchema,
summary: 'Create user'
}, async (c, body) => {
// Votre logique ici
}, { security: true })
// Autres méthodes disponibles :
this.createListRoute() // GET avec pagination
this.createGetByIdRoute() // GET /{id}
this.createPutRoute() // PUT /{id}
this.createDeleteRoute() // DELETE /{id}
```
#### ⚡ Route Utilities
```typescript
import { Route } from '@arvox/backend-framework'
// Pour plus de flexibilité :
this.controller.openapi(
Route.post('/users', {
request: CreateUserSchema,
response: UserSchema
}, {
tag: 'Users',
summary: 'Create user',
security: true
}),
async (c) => {
// Votre handler ici
}
)
```
#### 📊 Avantages de la Simplification
| Approche | Lignes de Code | Avantages |
|----------|----------------|-----------|
| `createRoute` original | ~40 lignes | Contrôle total |
| Méthodes `BaseController` | ~15 lignes | -60% code, type safety |
| `Route` utilities | ~12 lignes | -70% code, réutilisable |
**Fonctionnalités automatiques :**
- ✅ Schémas de réponse standardisés
- ✅ Gestion d'erreurs communes (400, 401, 404)
- ✅ Documentation OpenAPI complète
- ✅ Validation automatique des paramètres
- ✅ Type safety complet avec TypeScript
### Validation
```typescript
import { ValidationUtil } from 'arvox-backend'
// Schémas prédéfinis
const user = ValidationUtil.validate(data, z.object({
email: ValidationUtil.emailSchema,
password: ValidationUtil.passwordSchema
}))
// Validation de fichiers
const fileSchema = ValidationUtil.createFileSchema(['image/jpeg', 'image/png'], 5 * 1024 * 1024)
```
### Pagination
```typescript
import { PaginationUtil } from 'arvox-backend'
const paginationUtil = new PaginationUtil()
const { page, limit, skip } = paginationUtil.extractFromContext(c)
const result = paginationUtil.createResponse(items, total, page, limit)
```
### Réponses Standardisées
```typescript
import { ResponseUtil } from 'arvox-backend'
const responseUtil = new ResponseUtil()
// Réponses de succès
return responseUtil.success(data)
return responseUtil.paginated(items, total, page, limit)
// Réponses d'erreur
return responseUtil.error('Message d\'erreur')
return responseUtil.notFound('User')
return responseUtil.unauthorized()
```
## 📚 Documentation API
Une fois votre application démarrée, la documentation Swagger est automatiquement disponible sur :
- Documentation interactive : `http://localhost:3000/docs`
- Spécification OpenAPI : `http://localhost:3000/openapi.json`
## 🔧 Configuration Avancée
```typescript
const framework = new ArvoxFramework({
appName: 'Mon API',
version: '1.0.0',
port: 3000,
environment: 'production',
// Configuration CORS
cors: {
origin: ['https://monsite.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
headers: ['Content-Type', 'Authorization']
},
// Configuration des logs
logging: {
requests: true,
errors: true
},
// Configuration de sécurité
security: {
headers: true
}
})
```
## 🧪 Tests
Le framework inclut des classes de base qui facilitent les tests unitaires :
```typescript
import { beforeEach, describe, expect, it, vi } from 'vitest'
describe('CreateUserUseCase', () => {
let useCase: CreateUserUseCase
let mockRepository: UserRepository
beforeEach(() => {
mockRepository = {
create: vi.fn()
} as any
useCase = new CreateUserUseCase(mockRepository)
})
it('should create user successfully', async () => {
const userData = { name: 'John', email: 'john@example.com' }
const mockUser = { id: '1', ...userData }
vi.mocked(mockRepository.create).mockResolvedValue(mockUser)
const result = await useCase.execute(userData)
expect(result.success).toBe(true)
expect(result.data).toEqual(mockUser)
})
})
```
## 🤝 Contribution
Les contributions sont les bienvenues ! Merci de suivre les guidelines du projet.
## 📄 Licence
MIT License - voir le fichier LICENSE pour plus de détails.
## 🆘 Support
Pour toute question ou problème, n'hésitez pas à ouvrir une issue sur GitHub.