fhyts

FhyTS

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, and Secure (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).