@fuzionx/framework
v0.1.23
Published
Full-stack MVC framework built on @fuzionx/core — Controller, Service, Model, Middleware, DI, EventBus
Maintainers
Readme
@fuzionx/framework
Laravel-inspired full-stack MVC framework powered by Rust N-API bridge — 500K+ RPS
@fuzionx/framework ← npm install (개발자가 설치)
└── @fuzionx/core ← HTTP 엔진 (libuv Fusion, WebSocket, 세션, 크립토)
└── @fuzionx/bridge ← Rust N-API 네이티브 모듈Quick Start
# 글로벌 CLI 설치 (fx + create-fuzionx)
npm install -g create-fuzionx
# 프로젝트 생성
fx new my-app
# 또는: npx create-fuzionx my-app
cd my-app
npm install
# 개발 서버
fx dev # 또는: npx fx dev
# 또는 직접 실행
node app.js서버가 http://localhost:49080 에서 시작됩니다.
프로젝트 구조
my-app/
├── fuzionx.yaml # 서버 + DB + 세션 설정
├── .env # 환경변수
├── app.js # 엔트리포인트
│
├── routes/ # 라우트 정의
│ ├── api.js
│ └── web.js
│
├── controllers/ # HTTP 컨트롤러 (자동 스캔)
│ └── UserController.js
│
├── models/ # ORM 모델 (자동 스캔)
│ └── User.js
│
├── services/ # 비즈니스 로직 (자동 스캔)
│ └── UserService.js
│
├── middleware/ # 미들웨어 클래스 (자동 스캔)
│ └── AuthMiddleware.js
│
├── ws/ # WebSocket 핸들러 (자동 스캔)
├── jobs/ # 스케줄러/큐 Job (자동 스캔)
├── events/ # 이벤트 리스너
│
├── views/ # 템플릿 (Tera SSR / Bridge 렌더링)
│ └── default/
│ ├── layouts/
│ │ └── main.html
│ └── pages/
│ └── home.html
│
├── locales/ # i18n 번역 파일
│ ├── ko.yaml
│ └── en.yaml
│
├── storage/ # 로그, 업로드
└── public/ # 정적 파일엔트리포인트 (app.js)
import { Application } from '@fuzionx/framework';
import apiRoutes from './routes/api.js';
import webRoutes from './routes/web.js';
const app = new Application({ configPath: './fuzionx.yaml' });
// 라우트 등록
app.routes(apiRoutes);
app.routes(webRoutes);
// 서비스 등록 (Lazy 싱글톤)
app.register('mailer', () => new MailService(app.config.get('app.mail')));
// 라이프사이클 훅
app.on('booted', async () => {
console.log(`DB 연결 완료`);
});
// 서버 시작
app.listen(49080, () => {
console.log('🚀 FuzionX running on http://localhost:49080');
});라우팅 & 컨트롤러
라우트 파일
// routes/api.js
import UserController from '../controllers/UserController.js';
import Joi from 'joi';
export default (r) => {
r.group('/api', { middleware: ['apiAuth'] }, (r) => {
r.group('/users', (r) => {
r.get('/', UserController.index);
r.get('/:id', UserController.show);
r.post('/', UserController.store, {
validate: { body: Joi.object({
name: Joi.string().min(2).required(),
email: Joi.string().email().required(),
}) }
});
r.put('/:id', UserController.update);
r.delete('/:id', UserController.destroy);
});
});
};// routes/web.js
import HomeController from '../controllers/HomeController.js';
export default (r) => {
r.get('/', HomeController.index);
r.get('/about', HomeController.about);
};컨트롤러
// controllers/UserController.js
import { Controller } from '@fuzionx/framework';
export default class UserController extends Controller {
async index(ctx) {
const users = await this.db.User.paginate(ctx.query.page || 1, 20);
ctx.json(users);
}
async show(ctx) {
const user = await this.db.User.findOrFail(ctx.params.id);
ctx.json(user);
}
async store(ctx) {
const user = await this.service('UserService').register(ctx.body);
ctx.status(201).json(user);
}
async update(ctx) {
const user = await this.db.User.findOrFail(ctx.params.id);
await user.update(ctx.body);
ctx.json(user);
}
async destroy(ctx) {
await this.service('UserService').deactivate(ctx.params.id);
ctx.status(204).end();
}
}미들웨어는 글로벌 → 그룹 → 라우트별 3단계로 적용됩니다.
Context (ctx)
모든 핸들러, 미들웨어에 전달되는 통합 객체:
Request
ctx.method // 'GET', 'POST', ...
ctx.url // '/users/42?page=1'
ctx.path // '/users/42'
ctx.params // { id: '42' }
ctx.query // { page: '1' }
ctx.body // JSON 자동 파싱된 요청 바디
ctx.headers // 요청 헤더
ctx.ip // 클라이언트 IP
ctx.user // 인증된 사용자 (미들웨어에서 설정)
ctx.session // 세션
ctx.cookies // 파싱된 쿠키 객체
ctx.locale // 현재 locale ('ko', 'en')
ctx.files // 업로드된 파일 메타데이터 배열
ctx.get(header) // 헤더 값
ctx.is(type) // Content-Type 확인
ctx.accepts('json','html') // Accept 협상
ctx.t(key, vars) // i18n 번역Response
ctx.json(data) // JSON 응답
ctx.status(201).json(data) // 상태 코드 + JSON
ctx.send(html) // HTML 응답
ctx.text(string) // text/plain
ctx.render('home', { title }) // 템플릿 렌더링 (Rust Bridge SSR)
ctx.redirect('/login') // 302 리다이렉트
ctx.redirect('/new', 301) // 301 영구 리다이렉트
ctx.back() // Referer로 리다이렉트
ctx.error(404, 'Not found') // 에러 응답
ctx.download(filePath) // 파일 다운로드
ctx.stream(readable) // 스트림 응답
ctx.setHeader(key, value) // 헤더 설정
ctx.cookie(name, value, opts) // 쿠키 설정
ctx.end() // 응답 종료Model (ORM)
// models/User.js
import { Model } from '@fuzionx/framework';
export default class User extends Model {
static table = 'users';
static timestamps = true;
static hidden = ['password'];
static columns = {
id: { type: 'increments' },
name: { type: 'string', length: 100 },
email: { type: 'string', length: 150, unique: true },
password: { type: 'string', length: 255 },
active: { type: 'boolean', default: true },
};
posts() { return this.hasMany('Post'); }
profile() { return this.hasOne('Profile'); }
static findByEmail(email) {
return this.where('email', email).first();
}
}지원 드라이버: SQLite · MariaDB · PostgreSQL · MongoDB
Service
// services/UserService.js
import { Service } from '@fuzionx/framework';
export default class UserService extends Service {
async register(data) {
if (await this.db.User.findByEmail(data.email)) {
throw this.error('이미 존재하는 이메일');
}
const user = await this.transaction(async (trx) => {
const user = await this.db.User.create(data, { trx });
await this.db.Profile.create({ userId: user.id }, { trx });
return user;
});
this.emit('user:created', user);
return user;
}
}Middleware
// middleware/AuthMiddleware.js
import { Middleware } from '@fuzionx/framework';
export default class AuthMiddleware extends Middleware {
static name = 'auth';
handle(ctx, next) {
const token = ctx.headers.authorization?.replace('Bearer ', '');
if (!token) return ctx.error(401, 'Unauthorized');
ctx.user = this.service('AuthService').verify(token);
next();
}
}내장 미들웨어
| 이름 | 기능 | 처리 |
|------|------|------|
| bodyParser | JSON/Form 바디 파싱 | JS |
| cors | CORS 헤더 | Rust |
| csrf | CSRF 토큰 검증 | JS |
| session | 세션 로드/저장 | Rust |
| rateLimit | 요청 제한 | Rust |
| static | 정적 파일 서빙 | Rust |
WebSocket
// ws/ChatHandler.js
import { WsHandler } from '@fuzionx/framework';
export default class ChatHandler extends WsHandler {
static namespace = '/chat';
static middleware = ['auth'];
static events(e) {
e.on('chat', this.handleChat);
e.on('typing', this.handleTyping);
}
handleChat(socket, data) {
socket.to(`room:${data.roomId}`).send({
type: 'chat',
data: { user: socket.user, message: data.message },
});
}
}설정 (fuzionx.yaml)
bridge:
port: 49080
workers: 4 # 0 = CPU 수 자동
cors:
enabled: true
origins: ["*"]
rate_limit:
enabled: true
per_ip: 1000
session:
enabled: true
store: memory # memory | redis
ttl: 3600
logging:
level: info
database:
main:
driver: sqlite
path: ./storage/database.sqlite
app:
name: 'My App'
environment: ${NODE_ENV:development}
auth:
secret: ${JWT_SECRET:change-me}
accessTtl: '15m'
i18n:
default_locale: 'ko'환경변수 치환
host: ${DB_HOST:127.0.0.1} # 없으면 기본값 사용
password: ${DB_PASSWORD} # 필수 — 없으면 부트 시 에러
redis_url: ${REDIS_URL:} # 선택 — 없으면 빈 문자열Config 접근
app.config.get('bridge.port') // 49080
app.config.get('app.auth.secret') // JWT secret
app.config.get('app.payment.api_key', null) // 커스텀 설정 + 기본값CLI
프로젝트 생성 (create-fuzionx)
npx create-fuzionx my-app # 새 프로젝트 스캐폴딩
cd my-app
npm install프로젝트 내 명령어 (fx)
npx fx make:controller User # CRUD 컨트롤러
npx fx make:model User # ORM 모델
npx fx make:service User # 서비스
npx fx make:middleware Auth # 미들웨어
npx fx make:job Cleanup # 스케줄 Job
npx fx make:task SendEmail # 큐 Task
npx fx make:ws Chat # WebSocket 핸들러
npx fx make:event user # 이벤트 핸들러
npx fx make:worker Heavy # Worker (worker_threads)
npx fx make:test User # 테스트
# DB
npx fx db:sync # 모델 ↔ DB 스키마 diff
npx fx db:sync --apply # 안전 변경 적용
# 개발
npx fx dev # 개발 서버 (--watch)
npx fx dev --port=4000 # 포트 지정
npx fx test # 테스트 실행 (vitest)
npx fx routes # 라우트 테이블 출력
npx fx config # 설정 파싱 결과 출력부트스트랩 순서
app.js 실행
├── 1. Config 로드 fuzionx.yaml 파싱
├── 2. .env 로드 환경변수 주입
├── 3. Bridge 부트 Rust 네이티브 초기화
│ ★ emit('booting')
├── 4. DB 연결 Knex/Mongoose
├── 5. 모델 로드 models/ 자동 스캔
│ ★ emit('booted')
├── 6. i18n 로드 locales/ 번역 파일
├── 7~12. 자동 스캔 services, middleware, controllers,
│ ws, jobs, events
│ ★ emit('ready')
└── 13. 서버 시작 startFusionServer(port)
★ emit('listening')라이프사이클 훅
| 훅 | 시점 | 용도 |
|---|------|------|
| booting | Config 로드 직후 | 서비스 등록, 환경 설정 |
| booted | DB + 모델 로드 완료 | 캐시 워밍 |
| ready | 모든 초기화 완료 | 준비 로그 |
| listening | 서버 시작 완료 | 포트 표시 |
| shutdown | SIGTERM/SIGINT | 리소스 정리 |
에러 처리
| 에러 타입 | HTTP | JSON | HTML |
|-----------|:----:|------|------|
| ValidationError | 422 | { error, fields } | flash + redirect |
| ServiceError | 400/403/404 | { error } | 테마 에러 페이지 |
| Error (dev) | 500 | { error, stack } | 에러 페이지 + 스택 |
| Error (prod) | 500 | { error } | 에러 페이지 |
주요 특징
- ⚡ 500K+ RPS — Rust N-API Bridge (libuv + SO_REUSEPORT)
- 🦀 Rust 네이티브 — HTTP, 세션, CORS, Rate Limit, 암호화 모두 Rust 처리
- 🎯 MVC 아키텍처 — Controller, Service, Model 분리
- 📦 자동 스캔 — controllers/, models/, services/ 등 자동 로드
- 🔌 WebSocket — 네임스페이스 기반 이벤트 라우팅
- 🗄️ Multi-DB ORM — SQLite, MariaDB, PostgreSQL, MongoDB
- 🔐 Auth & Session — JWT + 세션 (Memory/Redis)
- 📡 EventBus — 도메인 이벤트 + Hub 연동 (멀티서버)
- ⏰ Scheduler & Queue — Job (cron), Task (큐)
- 🌍 i18n — 다국어 지원,
ctx.t(), locale 자동 감지 - 🎨 Tera SSR — Rust 기반 템플릿 렌더링
- 📄 OpenAPI — 자동 Swagger 문서 생성
License
MIT
