Nymph is the Bun/TypeScript flavor of the Aegis Framework. It's a minimal but full-featured microframework for building web applications, ported from Ikaros (the PHP flavor) with idiomatic TypeScript patterns.
- Bun v1.0+
bun installimport { Nymph, Router, HTTP } from '@aegis-framework/nymph';
const nymph = new Nymph();
const router = nymph.container().get<Router>('Router');
router.get('/', () => {
return 'Hello, World!';
});
router.get('/api/greet/{name}', (name: string) => {
return { message: `Hello, ${name}!` };
});
router.port(3000);
router.listen();
console.log('Listening on http://localhost:3000');bun run app.tsRegister routes by HTTP method with parameter extraction:
router.get('/users/{id}', (id: string) => { /* ... */ });
router.post('/users', async () => { /* ... */ });
router.put('/users/{id}', (id: string) => { /* ... */ });
router.delete('/users/{id}', (id: string) => { /* ... */ });
router.any('/health', () => ({ status: 'ok' }));Route handlers that return objects are automatically serialized as JSON.
PSR-11 style container with lazy singletons:
import { Container } from '@aegis-framework/nymph';
const container = new Container();
container.set('db', () => DB.connect('user', 'pass', 'mydb'));
container.set('userService', (c) => new UserService(c.get('db')));
const users = container.get<UserService>('userService');Fluent SQL query builder with parameter binding (no SQL injection):
import { DB, Query } from '@aegis-framework/nymph';
const db = await DB.connect('user', 'pass', 'mydb');
const query = new Query(db);
await query
.select(['id', 'name', 'email'])
.from('users')
.where('active').equals(1)
.and('role').like('%admin%')
.orderBy([['name', 'ASC']])
.limit(10)
.commit();
const results = query.results();Abstract base class for database models:
import { Schema, Scheme } from '@aegis-framework/nymph';
class User extends Schema {
protected static name_ = 'users';
protected static hash_ = ['password'];
protected static invisible = ['password'];
static init() {
super.init();
this.scheme!
.string('name', 255).notNull('name')
.string('email', 255).notNull('email').unique('email')
.string('password', 255).notNull('password');
}
}
Schema.setDB(db);
User.init();
// CRUD
const user = await User.create({ name: 'Jane', email: 'jane@example.com', password: 'secret' });
const found = await User.get(1);
await User.update(1, { name: 'Jane Doe' });
await User.delete(1);Session and token-based auth with CSRF protection:
import { Session, Token, Authenticator, Password } from '@aegis-framework/nymph';
const session = new Session();
const token = new Token('32-byte-secret-key-for-branca!!', router);
const password = new Password();
const auth = new Authenticator(session, token, password, http);
// Session-based login
const hash = await password.hash('secret');
await auth.login('session', 'secret', hash, { id: 1 }, sessionId);
// Token-based login
const jwt = await auth.login('token', 'secret', hash, { id: 1 });Variable interpolation with XSS escaping, conditionals, and includes:
import { Template } from '@aegis-framework/nymph';
const tmpl = new Template(fileSystem, router);
tmpl.setContent(`
<h1>{{title}}</h1>
<p>{{{rawHtml}}}</p>
{{if loggedIn}}
<a href="/dashboard">Dashboard</a>
{{else}}
<a href="/login">Log in</a>
{{/if}}
`);
tmpl.setData('title', 'Welcome');
tmpl.setData('rawHtml', '<em>unescaped</em>');
tmpl.setData('loggedIn', true);
tmpl.compile();{{var}}— escaped output (XSS safe){{{var}}}— raw output{{if var}}...{{/if}}— conditionals{{if var}}...{{else}}...{{/if}}— if/else{>{scripts}<}/{>{styles}<}— asset linking
Generic iterable that works as both list and dictionary:
import { Collection } from '@aegis-framework/nymph';
const c = new Collection({ name: 'Nymph', version: '1.0' });
c.hasKey('name'); // true
c.get('name'); // 'Nymph'
c.set('lang', 'ts');
c.length(); // 3
for (const [key, value] of c) {
console.log(key, value);
}import { Crypt, Password } from '@aegis-framework/nymph';
// AES-256-GCM encryption
const crypt = await Crypt.create('my-secret-key');
const encrypted = await crypt.encrypt('sensitive data');
const decrypted = await crypt.decrypt(encrypted);
// Password hashing (Bun.password - argon2/bcrypt)
const pw = new Password();
const hash = await pw.hash('my-password');
const valid = await pw.compare('my-password', hash);
const generated = pw.generate(16);src/
├── Enum/ # HttpMethod, ContentType, DebugLevel, HttpStatus
├── Container/ # DI Container + NotFoundException
├── Collection.ts # Generic iterable collection
├── Util.ts # UUID generation
├── Text.ts # String manipulation
├── Json.ts # JSON parsing and access
├── FileSystem.ts # File operations (Bun.file)
├── Crypt.ts # AES-256-GCM encryption (Web Crypto)
├── Password.ts # Hashing (Bun.password)
├── Configuration.ts # Config file loading
├── DB.ts # MySQL connection (mysql2/promise)
├── Query.ts # SQL query builder
├── Scheme.ts # Table schema builder
├── Schema.ts # Abstract ORM base class
├── HTTP.ts # Content types and error responses
├── Route.ts # Route pattern matching
├── Request.ts # Web Standard Request wrapper
├── Router.ts # Bun.serve() router
├── Template.ts # Template engine
├── Debug.ts # Error handling
├── Session.ts # In-memory sessions + CSRF
├── Token.ts # Branca token encoding
├── Authenticator.ts # Session/token auth
├── Upload.ts # File upload handling
├── Nymph.ts # Bootstrap / service registration
└── index.ts # Barrel export
# Type check
bun run check
# Run tests
bun test| Package | Purpose |
|---|---|
branca |
Token encoding (Branca/Fernet) |
mysql2 |
MySQL database driver |
Everything else uses Bun built-ins: Bun.password, Bun.file(), Bun.write(), Bun.serve(), Bun.CryptoHasher, crypto.randomUUID(), Web Crypto API.
MIT