Kontas adalah framework web minimalis berbasis Bun yang fokus pada kesederhanaan & performa tinggi.
bun add kontas
// app.ts
import { Kontas } from "kontas";
const app = new Kontas();
app.start(3000); // default port: 3000
Kontas menggunakan file-based routing yang simple & powerful:
-
./routes
&./middleware.ts
(default) -
./src/routes
&./src/middleware.ts
(fallback) - Custom path via config
// routes/serve.ts
export async function GET(ctx: Context) {
return { message: "Hello World!" };
}
- Object (auto-JSON)
return { status: "success" };
- Config Format
return {
data: { status: "success" },
config: {
headers: { 'X-Custom': 'Value' },
status: 201
}
};
- Raw Response
return new Response('Hello', {
headers: { 'Content-Type': 'text/plain' }
});
// middleware.ts
import type { MiddlewareFunction } from "kontas";
const auth: MiddlewareFunction = async (ctx) => {
const token = ctx.request.headers.get('Authorization');
if (!token) return new Response('Unauthorized', { status: 401 });
ctx.user = { id: 1, role: 'user' };
};
export default [
{
middlewares: [auth],
paths: ['/products']
}
];
// routes/users/[id]/serve.ts
export async function GET(ctx: Context) {
const { id } = ctx.params; // string
return { userId: id };
}
// routes/products/serve.ts
export async function GET(ctx: Context) {
// /products?sort=desc&filter=active
const sort = ctx.query.get('sort'); // 'desc'
const filter = ctx.query.get('filter'); // 'active'
// Multiple values
// /products?tags=js&tags=ts
const tags = ctx.query.getAll('tags'); // ['js', 'ts']
return { sort, filter, tags };
}
Kontas mendukung beberapa pattern routing:
-
/about
-> Static route -
/users/[id]
-> Dynamic route -
/blog/[...slug]
-> Catch-all route -
/products/[id]/reviews/[reviewId]
-> Nested dynamic - Query string otomatis tersedia via
ctx.query
Context<T>
menyediakan:
-
params
: Route parameters (string | string[]) -
query
: URLSearchParams (dari query string) -
request
: Raw Request -
body?
: Request body (type T) -
user?
: Custom user data -
headers
: Response headers -
setHeader(name, value)
: Set header -
setStatus(status)
: Set status -
status?
: Response status
export async function GET(ctx: Context) {
try {
throw new Error('Something went wrong');
} catch (err) {
return {
data: { error: err.message },
config: { status: 500 }
};
}
}
// routes/docs/[...slug]/serve.ts
export async function GET(ctx: Context) {
const slug = ctx.params.slug; // string[]
return { path: slug.join('/') };
}
const app = new Kontas({
port: 3000,
routesDir: './api/routes',
cors: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400'
}
});
Pattern matching di paths:
-
/products
= Exact match -
/orders/*
= Single wildcard -
/api/**
= Nested wildcard -
*/settings
= Suffix match -
**/profile
= Nested suffix match
const middlewares: MiddlewareGroup[] = [
{
middlewares: [auth, rateLimit],
paths: ['/api/**', '*/settings']
}
];
Kontas menyediakan class Code
utk handle error dgn format yg konsisten & otomatis.
// routes/products/serve.ts
import { Code, type Context } from "kontas";
export async function GET(ctx: Context) {
// Cara 1: Langsung code aja (default 500)
throw new Code('SOMETHING_WRONG');
// Cara 2: Status + code
throw new Code(403, 'FORBIDDEN');
// Cara 3: Status + code + data tambahan
throw new Code(400, 'INVALID_INPUT', { field: 'email' });
// β Invalid status - TypeScript error
throw new Code(199, 'INVALID'); // Error: 199 not assignable to type HttpStatus
}
Error handler utk custom message:
// errorHandler.ts
import { Code } from "kontas";
export default function errorHandler(error: Code) {
const { errorCode } = error;
// success: false udh otomatis dari Code class
switch(errorCode) {
case 'NOT_FOUND':
return {
message: 'Data tidak ditemukan!',
data: error.data // Optional
};
case 'INVALID_CREDENTIAL':
return {
message: 'Username atau password salah!'
};
case 'FORBIDDEN':
return {
message: 'Kamu gk punya akses!'
};
default:
return {
message: 'Ada kesalahan di server!'
};
}
}
Features:
- Format error yg konsisten & otomatis
- Type-safe HTTP status codes (200, 201, 400, 401, etc)
- Custom error message per kode
- Auto set
success: false
- Support data tambahan via
error.data
- Type-safe dgn TypeScript
- Hot reload support
Response Format (otomatis):
{
"success": false,
"message": "Pesan error sesuai kode",
"data": {} // Optional data tambahan
}
Valid HTTP Status Codes:
- 200, 201 (Success)
- 400, 401, 403, 404, 405 (Client Error)
- 500, 502, 503, 504 (Server Error)
const app = new Kontas({
rateLimit: {
windowMs: 15 * 60 * 1000, // 15 menit
max: 100 // max 100 requests per window
}
});
const app = new Kontas({
rateLimit: {
windowMs: 60 * 1000, // 1 menit
max: 30, // max 30 requests per minute
message: 'Terlalu banyak request!',
statusCode: 429, // Too Many Requests
skipFailedRequests: true, // Skip non-2xx responses
skipSuccessfulRequests: false,
keyGenerator: (ctx) => {
// Custom key (e.g., by API key)
return ctx.request.headers.get('x-api-key') || 'anonymous';
}
}
});
Rate Limit Info Headers:
-
X-RateLimit-Limit
: Max requests per window -
X-RateLimit-Remaining
: Sisa requests -
X-RateLimit-Reset
: Timestamp reset (ms) -
Retry-After
: Seconds to wait (saat limit tercapai)
const app = new Kontas({
securityHeaders: {
xFrameOptions: 'DENY', // Prevent clickjacking
xContentTypeOptions: 'nosniff', // Prevent MIME-type sniffing
xXSSProtection: '1; mode=block', // Enable XSS protection
referrerPolicy: 'strict-origin-when-cross-origin'
}
});
const app = new Kontas({
securityHeaders: {
// Prevent clickjacking
xFrameOptions: 'DENY',
// Prevent MIME-type sniffing
xContentTypeOptions: 'nosniff',
// Force HTTPS (HSTS)
strictTransportSecurity: {
maxAge: 31536000, // 1 tahun
includeSubDomains: true,
preload: true
},
// XSS protection
xXSSProtection: '1; mode=block',
// Control referrer info
referrerPolicy: 'strict-origin-when-cross-origin',
// Content Security Policy
contentSecurityPolicy: {
'default-src': ["'self'"],
'script-src': ["'self'", "'unsafe-inline'"],
'style-src': ["'self'", "'unsafe-inline'"],
'img-src': ["'self'", 'data:', 'https:'],
'font-src': ["'self'"],
'connect-src': ["'self'", 'https://api.example.com']
},
// Permissions Policy
permissionsPolicy: {
camera: ['none'],
microphone: ['none'],
geolocation: ['self'],
'payment-features': ['self']
}
}
});
Available Security Headers:
-
X-Frame-Options
: Mencegah clickjacking attacks -
X-Content-Type-Options
: Mencegah MIME-type sniffing -
Strict-Transport-Security
: Memaksa pake HTTPS -
X-XSS-Protection
: Browser built-in XSS protection -
Referrer-Policy
: Kontrol info referrer -
Content-Security-Policy
: Kontrol resource loading -
Permissions-Policy
: Kontrol fitur browser
const app = new Kontas({
csrf: {
enabled: true,
secret: 'your-random-secret-key' // WAJIB diganti!
}
});
const app = new Kontas({
csrf: {
enabled: true,
secret: 'your-random-secret-key',
// Custom names
cookieName: 'my-csrf-token',
headerName: 'x-my-csrf-token',
// Cookie settings
cookieOptions: {
httpOnly: true,
secure: true,
sameSite: 'Strict',
path: '/',
maxAge: 3600 // 1 jam
},
// Skip CSRF check untuk paths tertentu
ignorePaths: [
'/webhook',
'/api/public/*'
],
// Skip CSRF check untuk methods tertentu
ignoreMethods: ['GET', 'HEAD', 'OPTIONS']
}
});
Dengan Fetch:
// CSRF token otomatis ada di cookie
fetch('/api/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-csrf-token': 'token-from-cookie' // WAJIB sama dengan cookie
},
body: JSON.stringify(data)
});
Dengan Axios:
// Axios bisa auto handle CSRF
axios.defaults.withCredentials = true; // Enable cookies
CSRF Features:
- Double submit cookie pattern
- Zero server-side storage
- Auto-refresh token per request
- Configurable per path/method
- Secure defaults (httpOnly, secure, etc)
const app = new Kontas({
sanitizer: {
enabled: true,
rules: {
removeXSS: true,
stripTags: true,
escapeHTML: true
}
}
});
const app = new Kontas({
sanitizer: {
enabled: true,
rules: {
// Basic security
removeXSS: true, // <script>alert(1)</script> -> alert(1)
stripTags: true, // <p>text</p> -> text
escapeHTML: true, // <>&'" -> <>&'"
// Content cleaning
trimSpaces: true, // " text " -> "text"
removeEmoji: true, // "hello π" -> "hello "
maxLength: 1000, // Truncate panjang string
// Allow specific HTML
allowedTags: ['b', 'i', 'em', 'strong'],
allowedAttributes: {
'a': ['href', 'title'],
'img': ['src', 'alt']
},
// Custom rules
customRules: [
{
pattern: /badword/gi,
replace: '****'
}
]
},
// Skip sanitize untuk paths tertentu
ignorePaths: [
'/admin/*',
'/api/trusted/*'
],
// Atau sanitize HANYA paths tertentu
onlyPaths: [
'/api/comments/*',
'/api/posts/*'
]
}
});
Input Sanitization Features:
- Auto sanitize request body
- XSS & injection protection
- Path-based rules
- Custom sanitization rules
- Support JSON & form data
- Recursive object sanitization
Example Input & Output:
// Input dari client
const input = {
title: '<script>alert("xss")</script>Hello!',
desc: ' <b>Some text</b> with emoji π ',
html: '<p onclick="evil()">Click me</p>',
comment: 'Contains badword here'
};
// Output setelah sanitize
const output = {
title: 'Hello!', // script removed
desc: 'Some text with emoji', // tags stripped & emoji removed
html: '<p>Click me</p>', // onclick removed
comment: 'Contains **** here' // custom rule applied
};
const app = new Kontas({
dev: {
hotReload: true,
watchDirs: ['routes', 'middleware']
}
});
const app = new Kontas({
port: 3000,
portFallback: true // Auto switch if port is taken
});
const app = new Kontas({
dev: {
showRoutes: true // Display all registered routes
}
});
// routes/users/[id]/posts/[postId]/serve.ts
export async function GET(ctx: Context) {
// URL: /users/123/posts/456?sort=desc
const { id, postId } = ctx.params;
const sort = ctx.query.get('sort');
return { userId: id, postId, sort };
}
// routes/docs/[...slug]/serve.ts
export async function GET(ctx: Context) {
// URL: /docs/api/v1/users
const slug = ctx.params.slug; // ['api', 'v1', 'users']
return { path: slug.join('/') };
}
export async function POST(ctx: Context) {
const form = await ctx.request.formData();
const file = form.get('file') as File;
await Bun.write('./uploads/' + file.name, file);
return { filename: file.name };
}
export async function GET(ctx: Context) {
try {
throw new Error('Something went wrong');
} catch (err) {
return {
data: { error: err.message },
config: { status: 500 }
};
}
}
- File Upload Validation
- Database Integration
- WebSocket Support
- Advanced Caching
- GraphQL Support
- Hot Reload: Auto-reload routes & middleware saat ada perubahan
- Port Management: Auto-detect & switch ke port available
- Route Summary: Display registered routes & methods
- Error Handling: Detailed error messages & validations
Jika Anda memiliki pertanyaan atau masalah:
- Buat issue di repository GitHub kami
- Bergabung dengan komunitas Discord kami
- Kunjungi dokumentasi online kami
- Hubungi tim support kami