Get Started
FhyTS is a TypeScript-based web framework designed to be lightweight, modular, and flexible. With its clean architecture and support for dependency injection, middleware, and a simple routing system, FhyTS is suitable for building a wide range of web applications, from small to large.
Recomended Requirements
- Node.js v22 or higher
- TypeScript v5 or higher
- A configured tsconfig.json file (recommended)
Installation
To start using FhyTS in your project, simply install it via NPM:
npm install fhyts
Create Starter App
If you want to quickly scaffold a new project with FhyTS and best practices pre-configured (TypeScript, routing, DI, etc), use the starter CLI:
npx create-fhyts-app@latest
Alternatively, you can also start by cloning the starter repository directly via git:
git clone https://github.com/fitri-hy/fhyts.git my-app
cd my-app
npm install
npm start
This CLI is the recommended way to start a new FhyTS project with zero configuration.
Folder Structure Recommendations
my-app/
├── app/
│ ├── controllers/ # Controller app
│ ├── middlewares/ # Middleware app
│ ├── models/ # Model app
│ ├── services/ # Service app
│ ├── views/ # Template views (.ejs)
│ │ ├── layouts/ # Layout templates
│ │ ├── partials/ # Partial templates
│ │ └── home.ejs # Home page
│ └── routes.ts # Route registrasi app
│
├── config/ # Config files
│ └── App.Config.json
│
├── public/ # Static assets (css, js, images)
│
├── app.ts # Entry point
├── package.json # Project configuration and npm dependencies
├── tsconfig.json # TypeScript configuration
└── README.md
Next
After installation, you can Creating Server.
Create Server
This guide will walk you through creating a simple HTTP server using FhyTS.
Project Structure
You can start a simple project with a structure that looks like this:
my-app/
├── server.ts # Application entry point
├── tsconfig.json # TypeScript configuration
└── package.json # Dependencies and scripts
TypeScript Configuration
FhyTS is TypeScript-based, so you'll need a tsconfig.json file in your project root:
{
"compilerOptions": {
"target": "es2020",
"module": "commonjs",
"esModuleInterop": true,
"rootDir": "./",
"outDir": "./dist",
"strict": true,
"moduleResolution": "node",
"skipLibCheck": true,
"baseUrl": "."
},
"include": ["**/*"]
}
This configuration ensures your TypeScript projects can compile and run smoothly with fhyts.
Creating a Server
Create a file named server.ts with the following contents:
import { FhyEngine, Logger } from 'fhyts';
async function Server() {
try {
const port = 3000;
// Running the server
Logger.info(`Starting server on http://localhost:${port}`);
await FhyEngine.getInstance().start(port);
} catch (err) {
Logger.error('Server failed to start:', err);
}
}
Server();
API Reference
Function / Class | Description |
---|---|
FhyEngine.getInstance() |
Retrieving a singleton instance from the fhyts engine. |
Logger.info(message) |
Displays log information to the console. |
Logger.error(message, err) |
Displays error logs to the console. |
Running the Server
Run the following commands to compile TypeScript and run the server:
npx tsc
node dist/server.js
If successful, you will see output like:
[INFO]: Starting server on http://localhost:3000
Next
Once the server is running successfully, you can proceed to Routes.
Routes
Routing is a mechanism for handling HTTP requests based on the method (GET, POST, PUT, DELETE, etc.) and URL path. In fhyts, routing is easily managed through the rq method on the router instance provided by the framework.
Creating Routes (routes.ts)
Import and Initialization
First, import FhyEngine and create a shortcut to register routes with specific HTTP methods.
import { FhyEngine } from 'fhyts';
// Create a shortcut function to register routes
const Use = ((r) => r.rq.bind(r))(FhyEngine.getInstance().r);
Route Function
Create a Route function that contains all your route definitions.
export function Route() {
Use('GET', '/', (req, res) => {
res.send('Welcome to the main page!');
});
Use('GET', '/users/:id', (req, res) => {
const { id } = req.params;
res.send(`User details with ID: ${id}`);
});
}
CRUD Routing Example
Here's an example of implementing CRUD routing for resource users:
import { FhyEngine } from 'fhyts';
const Use = (r => r.rq.bind(r))(FhyEngine.getInstance().r);
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
];
export function Route() {
// CREATE: Add a new user
Use('POST', '/users', async (req, res) => {
if (!req.body || !Object.keys(req.body).length) await req.parseBody();
const name = req.body?.name?.trim();
if (!name) return res.status(400).json({ message: 'Name is required' });
const newUser = { id: users.length + 1, name };
users.push(newUser);
res.status(201).json(newUser);
});
// READ ALL: Get a list of all users
Use('GET', '/users', (_, res) => res.json(users));
// READ ONE: Get user by ID
Use('GET', '/users/:id', (req, res) => {
const user = users.find(u => u.id === Number(req.params.id));
if (!user) return res.status(404).json({ message: 'User not found' });
res.json(user);
});
// UPDATE: Updates user data based on ID
Use('PUT', '/users/:id', async (req, res) => {
if (!req.body || !Object.keys(req.body).length) await req.parseBody();
const idx = users.findIndex(u => u.id === Number(req.params.id));
if (idx === -1) return res.status(404).json({ message: 'User not found' });
const name = req.body?.name?.trim();
if (!name) return res.status(400).json({ message: 'Name is required' });
users[idx] = { ...users[idx], name };
res.json(users[idx]);
});
// DELETE: Deletes user by ID
Use('DELETE', '/users/:id', (req, res) => {
const idx = users.findIndex(u => u.id === Number(req.params.id));
if (idx === -1) return res.status(404).json({ message: 'User not found' });
const deleted = users.splice(idx, 1)[0];
res.json(deleted);
});
}
async function main() {
Route();
await FhyEngine.getInstance().start(3000);
console.log('Server running at http://localhost:3000');
}
main().catch(console.error);
Use Function Parameters
Parameter | Type | Description |
---|---|---|
method |
string |
HTTP Methods ('GET', 'POST', 'PUT', 'DELETE', etc.) |
path |
string |
The URL path to be handled, can use dynamic parameters (e.g. /users/:id ) |
handler |
function |
Functions that handle requests and responses ( (req, res) => { ... } ) |
Request and Response Objects
- req.params: Contains URL parameters defined in the route (e.g., :id in /users/:id).
- req.body: Contains the request body data (typically used in POST and PUT).
- res.send(data): Sends text data as a response to the client.
- res.json(data): Sends JSON data as a response to the client.
- res.status(code): Sets the HTTP status of the response.
Enable
Call the Route() function before starting the server to register all routes:
import { Route } from './routes';
async function Server() {
// ...
Route(); // Register all routes
// ...server initialization
}
Next
You can proceed to Controllers to separate business logic from routing.
Controllers
A controller is a class that manages the logic for handling requests and responses from clients. Controllers separate the handling code for better structure and easier maintenance.
Simple Controller (ExampleController.ts)
import { Controller, Request, Response } from 'fhyts';
export class ExampleController extends Controller {
// Method that handles GET requests to the main page
async index(req: Request, res: Response) {
// Sends a simple text response with status 200 OK
return res.status(200).send('Welcome to FhyTS!');
}
}
Explanation:
- Controller is extended to use the framework's built-in features.
- index is the method that will be executed when a specific route calls this controller.
- req is the request object containing the request data from the client.
- res is the response object used to send a reply to the client.
- res.status(200).send() sends an HTTP status of 200 (OK) and text as a response.
Controller Usage
Typically, controllers are invoked by routing, for example:
import { ExampleController } from './ExampleController';
export function Route() {
const Example = new ExampleController();
Use('GET', '/', (req, res) => Example.index(req, res));
}
Next,
You can proceed to Templating to manage the rendering of dynamic HTML pages in your application.
Templating
The FhyTS framework provides a simple templating system for creating HTML pages. This system allows you to separate the view from the application logic, making the code more modular and easier to maintain.
Views Directory Structure
app/views/
├── layouts/
│ └── main.ejs # Main layout template
└── page.ejs # Content page template
- layouts/main.ejs: A base template containing common HTML structures, such as the , , and tags.
- page.ejs: A template for the page content that is rendered into the layout.
Layout Template ( main.ejs )
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%= title || 'FhyTS' %></title>
</head>
<body>
<%- content %>
</body>
</html>
- <%= title %> is used to safely insert the value of the title variable (HTML escape).
- <%- content %> is used to insert HTML content without escaping, allowing rendering of the page template within the layout.
Controller Using Views
import { Controller, Request, Response, Views } from 'fhyts';
export class PageController extends Controller {
private views = new Views();
async index(req: Request, res: Response) {
// Renders the 'page' template with the page title data
const page = this.views.render('page', {
title: 'Pages Example'
});
// Sends rendered HTML with status 200
return res.status(200).html(page);
}
}
- new Views() creates an instance to access the templating render function.
- render('page', { title: 'Pages Example' }) processes the views/page.ejs template file with the title variable data.
- res.status(200).html(page) sends the rendered result as an HTML response.
Next
Proceed to the Static Folder section to start managing static files and data in your application.
Static Folder
The Static Folder is used to serve static files (such as CSS, JS, images, etc.) from the public directory to the client via an HTTP server.
Usage
Import the Static Class
import { Static } from 'fhyts';
Initialize the Static Middleware
const staticMiddleware = new Static('./public');
Register the Middleware to the Server
Add this middleware to your server's middleware manager using the use()
method.
app.middlewareManager.use(staticMiddleware.handle.bind(staticMiddleware));
Full Example
import { Static } from 'fhyts';
async function Server() {
// Initialize your application/server
// ...
// Create an instance of Static Middleware
const staticMiddleware = new Static('./public');
// Register the middleware to the app
app.middlewareManager.use(staticMiddleware.handle.bind(staticMiddleware));
// Continue initializing the server
// ...
}
Explanation
- The
./public
folder is the directory that contains static files to be served via HTTP. - The
Static
middleware will handle requests for static files and respond with the appropriate files if found. - Ensure the middleware order is correct — typically, static middleware should be registered early so that static files can be accessed immediately before other logic is processed.
Next
Continue to the Models section to start managing your application's data structure and logic.
Models
Models in FhyTS are responsible for representing and managing application data.
Basic Structure
All models in FhyTS are derived from the Model base class provided by the fhyts engine:
import { Model } from 'fhyts';
Interface Contracts
It is recommended to define TypeScript interfaces to maintain data type consistency:
export interface User {
id: number;
name: string;
email: string;
}
Model Implementation
Models are used to manage data collections, whether from memory, files, databases, or other external sources:
export class UserModel extends Model {
private users: User[] = [
{ id: 1, name: 'Nova User', email: 'nova@example.com' },
{ id: 2, name: 'Jane Doe', email: 'jane@example.com' },
];
async findAll(): Promise<User[]> {
return this.users;
}
async findById(id: number): Promise<User | null> {
return this.users.find(u => u.id === id) || null;
}
}
API Model (Inherited)
Method | Description |
---|---|
constructor() |
Model initialization. Can be used to inject dependencies. |
boot() |
Optional lifecycle method for initial data load or binding. |
Best Practices
- Use TypeScript interfaces for all data entities.
- Place your model files in the app/models directory.
- Avoid placing complex business logic in models—use Services for that.
- Use asynchronous functions to anticipate future database integration.
Example File Locations
models/
└── UserModel.ts
Advanced Integration
Models can be used directly in controllers or services, as part of a dependency injection container, or invoked explicitly:
import { UserModel } from './models/UserModel';
const userModel = new UserModel();
const users = await userModel.findAll();
Note
- Models in FhyTS do not assume a database by default.
- For database integrations such as MySQL, MongoDB, or others, models can be extended with external adapters or the ORM of your choice.
Next
Once you understand how to use Models, you can move on to the Services section to manage business logic and integration between application components.
Services
Services in FhyTS act as a layer between the Controller and the Model, responsible for handling business logic, data processing, and managing the application's internal dependencies.
Basic Structure
All services are derived from the Service base class available in the framework:
import { Service } from 'fhyts';
Model Sharing
A service can access one or more models explicitly in its constructor.
import { UserModel, User } from '../models/UserModel';
export class UserService extends Service {
private userModel: UserModel;
constructor(userModel?: UserModel) {
super();
this.userModel = userModel || new UserModel();
}
async getUsers(): Promise<User[]> {
return await this.userModel.findAll();
}
async getUser(id: number): Promise<User | null> {
return await this.userModel.findById(id);
}
async execute(): Promise<void> {
// Optional override, used when the service is called as a task
}
}
General Contract
The Service class in FhyTS supports the single-responsibility principle and can be extended to meet various needs, such as:
Method | Description |
---|---|
constructor() |
Initialization location for dependencies such as models, utilities, or other helpers. |
execute() |
Optional lifecycle method (can be overridden) for custom execution. |
Best Practices
- Each service should focus on a single domain/entity (e.g., UserService, AuthService).
- Avoid performing validation or rendering within the service — focus solely on business logic.
Example Usage in Controller
import { UserService } from './services/UserService';
const userService = new UserService();
const userList = await userService.getUsers();
Integrations and Extensions
- Services can be used to access multiple models simultaneously.
- They can also consume external APIs, run asynchronous processes, caching, or queue.
- The execute() method can be used if the service is running as a task or background job.
Next
Once you understand how to use Services, you can move on to the Middlewares section to manage the intermediate logic layer in your application's request flow.
Middlewares
Middleware in FhyTS functions as an intermediary layer (interceptor) between requests and responses. Middleware can be used for various purposes, such as authentication, logging, parsing, validation, and caching.
Basic Structure
Middleware in FhyTS is defined using the MiddlewareFn type, which accepts three main arguments:
(req: Request, res: Response, next: () => Promise<void>) => Promise<void>
This type allows middleware to be executed asynchronously before the controller is called.
import { MiddlewareFn, Request, Response } from 'fhyts';
Simple Caching Middleware
const cache: Record<string, string> = {};
export const CacheMiddleware: MiddlewareFn = async (req: Request, res: Response, next) => {
const key = req.url;
if (cache[key]) {
res.status(200).html(cache[key]);
} else {
const originalSend = res.send.bind(res);
res.send = (data: string) => {
cache[key] = data;
originalSend(data);
};
await next();
}
};
Explanation:
- cache stores the URL-based response as a key.
- If the URL already exists in the cache, the response is returned directly without proceeding to the controller.
- If it doesn't exist, the original response is cached via the res.send method override.
Implementation in Routing
Middleware can be registered globally or per route in the routes.ts file.
import { CacheMiddleware } from './middlewares/CacheMiddleware';
// ....
Use('GET', '/', Example.index.bind(Example), [CacheMiddleware]);
Best Practices
- Middleware should be stateless unless absolutely necessary (such as caching).
- Ensure all middleware calls await next() unless it wants to stop the request flow.
- Use middleware to handle functions that are orthogonal to the core business (e.g., logging, rate-limiting, etc.).
Next
Setelah Anda memahami cara menggunakan Middleware, Anda dapat melanjutkan ke bagian Authentication mempelajari cara menangani autentikasi pengguna.
Authentication
Authentication (Auth) in FhyTS is used to manage the login, logout, and user authentication status checking process using a session-based strategy.
Basic Structure
All authentication services are provided by the Auth
object, which can be imported directly from fhyts
:
import { Auth } from 'fhyts';
const auth = new Auth();
auth.use(myStrategy); // Example of an authentication strategy
Auth
is the main class used to handle login, logout, user sessions, and access protection via middleware.
Project Structure
Minimal project structure to implement an authentication system:
my-app/
├── auth/
│ ├── auth.controller.ts # Controller for login, logout, profile
│ └── session.strategy.ts # Session-based authentication strategy
├── routes.ts # Application route definitions
├── server.ts # Application entry point
└── ...
Implementing a Session Strategy
Create the file auth/session.strategy.ts
which contains the logic for storing user sessions:
import { Request } from 'fhyts';
export class SessionStrategy {
private sessions = new Map<string, any>();
login(sessionId: string, user: any) {
this.sessions.set(sessionId, user);
}
logout(sessionId: string) {
this.sessions.delete(sessionId);
}
isAuthenticated(req: Request): boolean {
const sid = req.cookies['session_id'];
return sid ? this.sessions.has(sid) : false;
}
getUser(req: Request): any {
const sid = req.cookies['session_id'];
return sid ? this.sessions.get(sid) : null;
}
}
This strategy stores sessions in a
Map
in memory. For production, use external storage like Redis.
Creating an Auth Controller
Create the file auth/auth.controller.ts
to handle authentication requests:
import { Auth, Request, Response } from 'fhyts';
export class AuthController {
constructor(private auth: Auth) {}
async login(req: Request, res: Response) {
const { username, password } = req.body;
if (username === 'user' && password === 'pass') {
const sessionId = 'sess-' + Math.random().toString(36).slice(2);
const user = { id: 1, username };
this.auth.loginUser(sessionId, user);
res.setHeader('Set-Cookie', `session_id=${sessionId}; HttpOnly; Path=/; SameSite=Strict`);
res.status(200).json({ message: 'Login successful', user: { ...user, sessionId } });
} else {
res.status(401).json({ error: 'Incorrect username or password' });
}
}
async logout(req: Request, res: Response) {
const sessionId = req.cookies['session_id'];
const user = this.auth.getUser(req);
if (sessionId) {
this.auth.logoutUser(sessionId);
res.setHeader('Set-Cookie', 'session_id=; HttpOnly; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Strict');
}
res.status(200).json({ message: 'Logout successful', user });
}
async profile(req: Request, res: Response) {
if (!this.auth.isAuthenticated(req)) {
return res.status(401).json({ error: 'Unauthorized. Please login first.' });
}
const user = this.auth.getUser(req);
const sessionId = req.cookies['session_id'];
res.status(200).json({ message: 'Check logged-in user', user: { ...user, sessionId } });
}
}
Registering Routes
Create and register authentication routes in the routes.ts
file:
import { FhyEngine, Auth } from 'fhyts';
import { SessionStrategy } from './auth/session.strategy';
import { AuthController } from './auth/auth.controller';
const auth = new Auth();
auth.use(new SessionStrategy());
const authCtrl = new AuthController(auth);
const Use = ((r) => r.rq.bind(r))(FhyEngine.getInstance().r);
export function Route() {
Use('POST', '/login', authCtrl.login.bind(authCtrl));
Use('POST', '/logout', authCtrl.logout.bind(authCtrl), [auth.requireAuth]);
Use('GET', '/profile', authCtrl.profile.bind(authCtrl), [auth.requireAuth]);
}
Use
auth.requireAuth
to protect routes from access by unauthenticated users.
API Reference
Function / Class | Description |
---|---|
Auth.use(strategy) |
Registers an authentication strategy. |
auth.loginUser(sessionId, user) |
Saves the user session by sessionId . |
auth.logoutUser(sessionId) |
Deletes the user session. |
auth.getUser(req) |
Retrieves user data from the request based on the session. |
auth.isAuthenticated(req) |
Checks if the request comes from an authenticated user. |
auth.requireAuth |
Middleware to restrict access to routes that require login. |
SessionStrategy |
Simple session-based strategy using in-memory Map storage. |
Testing the Auth Flow
Login
POST /login
Content-Type: application/json
{
"username": "user",
"password": "pass"
}
Get Profile (after login)
GET /profile
Cookie: session_id=sess-xxxxxxxx
Logout
POST /logout
Cookie: session_id=sess-xxxxxxxx
Security Tips
Important tips to keep your authentication system secure:
- Use cookie attributes:
HttpOnly
,SameSite=Strict
, andSecure
(if using HTTPS). - Implement rate-limiting: limit login attempts within a certain timeframe to prevent brute-force attacks.
- Use external session storage: like Redis or a database for production — avoid storing sessions in a
Map
for stateless or large-scale apps. - Avoid storing sensitive data directly in sessions (e.g., password hashes, access tokens).
- Use encryption if storing sensitive data in sessions or cookies.
- Validate all inputs, especially at login endpoints.
- Check and clean expired sessions regularly to prevent abuse.
- Audit login/logout events for security and tracking.
- Use HTTPS for all endpoints handling authentication.
- Do not hardcode credentials — store them in
.env
or secure config files.
What Users Can Customize
The Auth
module in FhyTS is designed to be flexible. Here's a list of customizable components:
Component | Customizable? | Description |
---|---|---|
Authentication strategy | Yes | Create your own strategy like JWT, OAuth, API Key, etc. |
User object structure | Yes | Define freely the contents and structure of the user object stored in sessions. |
Login validation | Yes | Connect to DB, external API, or custom login system. |
Cookie setup | Yes | Cookie name, security options (Secure, HttpOnly), path, domain can be configured. |
Middleware | Yes | Add middleware for role-based access, rate limiting, etc. |
Storage backend | Yes | Use Redis, database, file, or memory as needed. |
Session parameter name | Yes | Change session_id cookie/token name if desired. |
Session duration (timeout) | Yes | Set sessions to expire automatically after a certain time. |
Event hooks (onLogin, etc.) | Yes | Add custom logic on successful login/logout (audit logs, etc). |
Multi-strategy support | Yes | Use multiple strategies simultaneously (e.g., session + token fallback). |
Auto login / remember me | Yes | Add “remember me” support with long-lived cookies. |
Custom error handler | Yes | Handle authentication errors according to your API format/standard. |
Next
After understanding Auth, next you can study the Request section to learn about handling request data in your application.
Request
The Request object in FhyTS is an abstraction of http.IncomingMessage that has been simplified and extended to facilitate the management of HTTP request data.
Access Common Properties
Here are the main properties and methods available from the Request object:
Property / Method | Type | Description |
---|---|---|
method |
string |
HTTP methods such as GET , POST , etc. |
url |
string |
The original URL of the request. |
headers |
IncomingHttpHeaders |
The headers of the request. |
body |
any |
The body of the request. Must be parsed first. |
params |
Record<string, string> |
Dynamic parameters from the URL (e.g., /user/:id ). |
query |
Record<string, string> |
Query parameters from the URL (?key=value ). |
cookies |
Record<string, string> |
Cookies sent by the client. |
getHeader(name) |
(name: string) => string | undefined |
Retrieves the value of a specific header. |
parseBody() |
Promise<void> |
Parses the body content for POST/PUT/PATCH methods. |
Usage Examples
Accessing Basic Information
export const index = async (req: Request, res: Response) => {
const method = req.method;
const userAgent = req.getHeader('user-agent');
const query = req.query;
res.json({ method, userAgent, query });
};
Parsing the Body (POST)
Before accessing req.body, be sure to call parseBody() first:
export const store = async (req: Request, res: Response) => {
await req.parseBody(); // Required for body parser
const { name, email } = req.body;
res.json({ received: { name, email } });
};
Accessing Parameters and Queries
// Route: /users/:id
export const show = async (req: Request, res: Response) => {
const userId = req.params.id;
const lang = req.query.lang || 'en';
res.json({ userId, lang });
};
Accessing Cookies
export const profile = async (req: Request, res: Response) => {
const session = req.cookies['session_id'];
res.json({ session });
};
Integration
The Request object is automatically available across controllers and middleware via the req parameter. It does not need to be manually initialized by the application developer.
Best Practices
- Always call await req.parseBody() for POST, PUT, or PATCH methods before accessing req.body.
- Explicitly validate the contents of body or params according to business needs.
- Avoid storing large amounts of data in req during chained middleware processes.
Next
Now that you understand how to use Response, you can move on to the Response section to learn how to manage HTTP responses in your application.
Response
The Response object in FhyTS is a wrapper for http.ServerResponse, which simplifies sending HTTP responses, including setting statuses, headers, and response formats like JSON and HTML.
API Reference
Here are the main properties and methods available:
Property / Method | Type | Description |
---|---|---|
headersSent | boolean | Indicates whether headers have been sent to the client. |
status(code) | (code: number) => this | Sets the HTTP response status (e.g., 200, 404, 500). |
setHeader(name, val) | (name: string, value: string) => this | Manually sets HTTP headers (if not already sent). |
json(data) | (data: any) => void | Sends a JSON response with the header Content-Type: application/json. |
html(html) | (html: string) => void | Sends an HTML response with the header Content-Type: text/html. |
send(data) | (data: string | Buffer) => void | Sends raw data to the client (string or buffer). |
Usage Examples
Sending JSON
export const api = async (req: Request, res: Response) => {
res.status(200).json({ message: 'Success', timestamp: Date.now() });
};
Sending HTML
export const page = async (req: Request, res: Response) => {
res.status(200).html('<h1>Welcome to FhyTS</h1>');
};
Manually Assign Headers
export const download = async (req: Request, res: Response) => {
res
.setHeader('Content-Disposition', 'attachment; filename="data.txt"')
.setHeader('Content-Type', 'text/plain')
.send('This is the file content.');
};
Automatic Integration
The Response object is automatically provided in the controller and middleware context via the res parameter. There's no need to manually create an instance.
export const handler = async (req: Request, res: Response) => {
res.status(201).json({ created: true });
};
Best Practices
- Make sure to only call one response method (json, html, or send) per request.
- Use headersSent to prevent header modification after the response is sent.
- Always set Content-Type explicitly when using send.
Next
Now that you understand how to use Response, you can move on to the Upload section to learn how to manage files.
Upload
The FhyTS framework supports file upload processes via the HTTP protocol using the POST method and the multipart/form-data content type. FhyTS provides a simple yet powerful mechanism to process form data and files sent by clients.
Basic Concept
Multipart/form-data is an encoding format used when clients send form data containing files.
- Fields: Text data from the form, such as username, description, etc.
- Files: File data, such as images, documents, and the like.
Best Practices
- Validate Content-Type: Ensure the
multipart/form-data
header is present. - Limit file size: Prevent overload and DoS attacks.
- Sanitize file names: Replace illegal characters for security.
- Handle errors: Use try-catch when parsing & saving files.
- Separate upload folder: Store files in a dedicated folder with restricted access.
- Store metadata: Record file information for easier management.
- Non-blocking I/O: Use async operations to keep the server responsive.
API Reference
Function / Property | Type | Description |
---|---|---|
req.headers |
IncomingHttpHeaders |
HTTP request headers, used to check Content-Type |
req.rawReq |
http.IncomingMessage |
The original request stream, used for parsing multipart |
parseMultipart(req.rawReq, req.headers) |
Promise<{fields: Record<string,string>, files: Record<string, UploadedFile[]>}> |
Function to parse form-data and file uploads |
res.status(code) |
(code: number) => this |
Set HTTP response status |
res.json(data) |
(data: any) => void |
Send response in JSON format |
Example Usage
import { Upload } from 'fhyts';
Use('POST', '/upload-image', async (req, res) => {
try {
if (!req.headers['content-type']?.includes('multipart/form-data')) {
return res.status(400).json({
message: 'Content-Type must be multipart/form-data'
});
}
const { fields, files } = await Upload(req.rawReq, req.headers);
const file = files.image?.[0];
if (!file || typeof file.filename !== 'string') {
return res.status(400).json({
message: 'No valid image file uploaded'
});
}
const uploadsDir = path.join(process.cwd(), 'uploads');
if (!fs.existsSync(uploadsDir)) fs.mkdirSync(uploadsDir);
const safeName = file.filename.replace(/[^a-z0-9.\-_]/gi, '_');
const filePath = path.join(uploadsDir, `${Date.now()}_${safeName}`);
fs.writeFileSync(filePath, file.data);
res.status(201).json({
message: 'Image uploaded successfully',
filename: file.filename,
mimetype: file.mimetype,
size: file.data.length,
savedAs: filePath,
fields,
});
} catch (err) {
console.error(err);
res.status(500).json({ message: 'Upload failed', error: String(err) });
}
});
Next,
Now that you understand how to use Response, you can move on to the DI Container section to learn how to efficiently manage dependencies and service injection in your application.
DI Container
FhyTS provides a lightweight and flexible Dependency Injection Container, allowing you to centrally and efficiently manage dependency instantiation and resolution.
General Concepts
DI Containers allow you to:
- Register services using factory functions.
- Retrieve service instances safely and efficiently.
- Ensure each service is created only once (singleton pattern).
API Reference
Method | Type | Description |
---|---|---|
register(key, factory) |
(key: string, factory: () => any) => void |
Registers a factory function for a given service. |
get(key) |
<T>(key: string) => T |
Retrieves a service instance based on its key. |
Usage Examples
Registration and Resolution
import { DIContainer } from 'fhyts';
import { UserService } from './services/UserService';
const container = new DIContainer();
// Registration
container.register('userService', () => new UserService());
// Use
const userService = container.get<UserService>('userService');
await userService.getUsers();
With Chained Dependencies
If the service has other dependencies:
container.register('userModel', () => new UserModel());
container.register('userService', () => {
const model = container.get<UserModel>('userModel');
return new UserService(model);
});
Characteristics
- Lazy Initialization: Objects are only initialized the first time they are requested.
- Singleton by default: Each service is instantiated only once.
- Factory-based: Does not rely on reflection or metadata.
Best Practices
- Use consistent and descriptive string keys (userService, authService, etc.).
- Register all services in one place (e.g., container.ts).
- Avoid creating instances directly outside the container, especially in large-scale applications.
Next
Once you understand how to use DI Containers, you can move on to the Config section to manage your application's configuration files in a flexible and structured way.
Config
Config in FhyTS is a singleton component responsible for loading and providing access to application configuration from an external file, such as JSON. This allows you to centrally and consistently manage the environment, application settings, and global parameters.
General Concepts
- Uses a singleton pattern, ensuring that only one instance of the configuration exists at runtime.
- Loads a configuration file from a specified path (typically config/App.Config.json).
- Provides secure access to configuration values with fallbacks/defaults.
API Reference
Method / Property | Type | Description |
---|---|---|
getInstance() |
static => Config |
Gets a single instance of Config . |
load(filePath) |
(filePath: string) => void |
Loads and parses a configuration file (JSON). |
get(key, defaultValue) |
(key: string, defaultValue?: any) => any |
Gets the configuration value based on the key, with the option of a default value. |
Usage Example
Loading Configuration at Entry Point
import { Config } from 'fhyts';
const config = Config.getInstance();
config.load('./config/App.Config.json');
Accessing Configuration Values
const appName = config.get('app_name', 'DefaultApp');
const isDebug = config.get('debug', false);
If the key is not found, then the default value will be returned.
Example JSON File Structure
// config/App.Config.json
{
"app_name": "FhyTS App",
"debug": true,
"port": 3000,
"minified": false
}
Characteristics
- Type Safe: Supports fallback/default values to prevent errors if a key is unavailable.
- Lazy Loading: Configuration data is only loaded when load() is called.
- Global Access: Can be used throughout the application because it is a singleton.
Best Practices
- Load the configuration file only once, at the application entry point (server.ts).
- Use get(key, defaultValue) to prevent errors when a key is unavailable.
- Use a consistent and documented structure in the JSON configuration file.
Next
Once you understand how to use Config, you can move on to the Modules section to learn how to dynamically load and manage individual modules in your application.
Modules
The Modules feature in FhyTS allows you to load, register, and manage additional features separately and modularly. This mechanism is ideal for plugin-based architectures, large-scale systems, or applications that require flexible extension points.
General Concepts
- A module is a standalone entity or feature that can be registered with the system.
- The Module class acts as a registry, storing all registered module instances.
- Registered modules can be accessed globally via getModules() for initialization, integration, or execution.
API Reference
Method | Type | Description |
---|---|---|
register(instance) |
(moduleInstance: any) => void |
Registers a module with the system. |
getModules() |
() => any[] |
Retrieves all registered module instances. |
Usage Example
Registering a Module
Suppose you have a custom module named AnalyticsModule:
class AnalyticsModule {
init() {
console.log('Analytics initialized');
}
}
Registration can be done as follows:
import { Module } from 'fhyts';
const moduleSystem = new Module();
moduleSystem.register(new AnalyticsModule());
Initialize All Modules
for (const mod of moduleSystem.getModules()) {
if (typeof mod.init === 'function') {
mod.init();
}
}
Best Practices
- Each module should have a standard interface/method (e.g., init() or boot()).
- Use a single Module instance for the entire application and manage registration centrally.
- Avoid hard-coding modules — use autoloading when needed for large scale.
Next
Once you understand how to use Modules, you can move on to the Utils section to learn about a collection of utility functions that support various operations in your application efficiently and reusably.
Utils
Utils is a collection of static helper functions provided by the FhyTS framework to simplify object manipulation and validation in various contexts, such as configuration merging, data type validation, and more.
API Reference
Utils.isObject(obj: any): boolean
Checks whether the given value is a regular object (not an array and not null).
- Parameter: obj — any value to check.
- Return: true if obj is a valid object, false otherwise
Example Usage:
import { Utils } from 'fhyts';
console.log(Utils.isObject({})); // true
console.log(Utils.isObject([])); // false
console.log(Utils.isObject(null)); // false
console.log(Utils.isObject('text')); // false
Utils.merge(target: any, source: any): any
Recursively merges properties from source to target. Suitable for merging configurations within an application.
Parameters:
- Target — the object to modify.
- Source — the source object to merge.
Return:
- The merged object with updated or added properties from source.
Usage Examples:
import { Utils } from 'fhyts';
const defaults = {
debug: false,
server: { port: 3000, host: 'localhost' }
};
const overrides = {
debug: true,
server: { port: 8080 }
};
const config = Utils.merge(defaults, overrides);
// config = { debug: true, server: { port: 8080, host: 'localhost' } }
Note
- Utils.merge() performs a deep merge, not a shallow merge.
- Utils.isObject() is useful for validation before manipulating object properties in dynamic functions.
Next
Once you understand how Utils works, you can move on to the Database section to learn how the framework handles integration and abstraction of data access using the models you've defined.
Database
FhyTS provides an interface-based database abstraction through the IDatabase contract, allowing you to flexibly implement database connections to MySQL, PostgreSQL, SQLite, MongoDB, or others.
Contract API: IDatabase
Every database driver or adapter must implement the following interface:
Method | Description |
---|---|
connect(): Promise<void> |
Initializes a connection to the database. |
disconnect(): Promise<void> |
Safely closes the database connection. |
query<T>(sql, params?) |
Executes a SQL command or data operation. Returns a Promise<T> . |
Abstract Class: Database
The framework provides an abstract class Database that implements IDatabase as a basis for concrete adapters. You can simply create an instance of this class to integrate your preferred database.
Implementation Example: MySQL
Here's an example of implementing a MySQL driver using the mysql2/promise library:
import mysql from 'mysql2/promise';
import { Database } from 'fhyts';
export class MySQLDatabase extends Database {
private pool!: mysql.Pool;
async connect(): Promise<void> {
this.pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'yourpassword',
database: 'yourdb',
waitForConnections: true,
connectionLimit: 10,
});
}
async disconnect(): Promise<void> {
await this.pool.end();
}
async query<T = any>(sql: string, params?: any[]): Promise<T> {
const [rows] = await this.pool.execute(sql, params);
return rows as T;
}
}
Usage in Services
Once you register a database instance, you can use it in a service or controller:
export class UserService {
constructor(private db: Database) {}
async getAllUsers() {
const users = await this.db.query('SELECT * FROM users');
return users;
}
}
Integration with DI Container
import { DIContainer } from 'fhyts';
import { MySQLDatabase } from './db/MySQLDatabase';
const container = new DIContainer();
container.register('db', () => {
const db = new MySQLDatabase();
db.connect(); // optional: can be managed async separately
return db;
});
Best Practices
- Create one driver class for each database type.
- Ensure that connect() is only called once at the start of the application.
- Use parameterized queries (?) to avoid SQL injection.
- Use interfaces to allow driver replacement without changing the service.
Next
Once you understand Database usage, you can move on to the Logger section to learn how to structuredly log application activity, debug, and errors using the framework's built-in logging system.
Logger
Logger is a simple yet effective built-in logging module designed to help you log application activity, warnings, errors, and debug information. This logger can be used throughout the application, whether in services, middleware, or modules.
Purpose
- Simplifies real-time application activity.
- Consistently differentiates message types (info, warn, error, debug).
- Supports debug output only in development mode.
API Reference
Method | Description |
---|---|
Logger.info(msg, ...) |
Logs general information. Used for normal logging. |
Logger.warn(msg, ...) |
Logs warnings. Typically for events that do not terminate the process. |
Logger.error(msg, ...) |
Logs errors. Typically when an exception or critical failure occurs. |
Logger.debug(msg, ...) |
Logs debug messages, only appears when NODE_ENV=development . |
Usage Examples
Logging General Information
import { Logger } from 'fhyts';
Logger.info('Server running on port 3000');
Note the Warning
Logger.warn('Cache not found, continue without cache');
Logging Errors
try {
throw new Error('Failed to connect to database');
} catch (err) {
Logger.error('Connection error:', err);
}
Best Practices
- Use info() for success messages or general application flow.
- Use warn() for abnormal but non-fatal conditions.
- Use error() to log exceptions, failures, or serious issues.
- Use debug() for development purposes; avoid it in production.
Next
Once you understand how to use Logger, you can move on to the Error Handler section to learn how to handle errors centrally and responsively in your application.
Error Handler
ErrorHandler is a built-in FhyTS component responsible for centralized error handling. It logs error details using a Logger and responds to clients in a consistent JSON format.
Purpose
- Provides global error handling for all HTTP requests.
- Prevents crashes due to unhandled exceptions.
- Ensures error messages are delivered to clients in a safe and structured manner.
API Reference
ErrorHandler.handle(err, req, res)
Handles error objects thrown during the HTTP request process.
Parameters | Type | Description |
---|---|---|
err |
any |
The error object to be captured. |
req |
Request |
The request object when the error occurred. |
res |
Response |
The response object for sending error responses. |
Response Format
{
"error": "Error message that occurs"
}
By default:
- Status code: 500 Internal Server Error
- Message: "Internal Server Error"
However, if err has a statusCode and message, those values will be used.
Usage Example
In a Manual HTTP Server
import { ErrorHandler } from 'fhyts';
try {
// ... main process
} catch (err) {
ErrorHandler.handle(err, req, res);
}
In Routing/Controller
export const exampleHandler = async (req: Request, res: Response) => {
try {
throw new Error('Something went wrong');
} catch (err) {
ErrorHandler.handle(err, req, res);
}
};
Logger Integration
All errors are automatically logged via Logger.error() with information about the method and URL that caused the error.
Example output:
[ERROR] Error on GET /users Error: Something went wrong
Best Practices
- Use an ErrorHandler as a fallback in the upper layers of your application.
- Add a statusCode property to your custom errors if you want to control the HTTP status:
const err = new Error('User not found');
err.statusCode = 404;
throw err;
- Avoid sending internal application details to production clients (control via env).