Frontend Hamroun
A lightweight full-stack JavaScript framework with Virtual DOM and hooks implementation

π Table of Contents
π Introduction
Frontend Hamroun is a lightweight (~5KB gzipped) JavaScript framework designed for building modern web applications. It combines React-like frontend development with powerful backend capabilities in a single, unified package.
Key Features
- Efficient Virtual DOM: Minimizes DOM operations with intelligent diffing
- Complete Hooks API: useState, useEffect, useRef, useMemo, and more
- Full-Stack Solution: Unified frontend and backend development
- Server-Side Rendering: Optimized SSR with hydration
- File-Based Routing: Intuitive API endpoint creation
- Database Integration: Built-in support for MongoDB, MySQL, PostgreSQL
- TypeScript Support: Full type definitions and excellent DX
- Automatic Batch Updates: Efficient state management
- CLI Tooling: Scaffolding for components, pages, and API routes
π¦ Installation
npm install frontend-hamroun
yarn add frontend-hamroun
pnpm add frontend-hamroun
π Quick Start
Method 1: Create a new project with CLI
npx create-frontend-app my-app
npx frontend-hamroun create my-app
Then:
cd my-app
npm install
npm run dev
This will scaffold a new project with all the necessary configuration.
Method 2: Add to an existing project
npm install frontend-hamroun
import { render, useState } from 'frontend-hamroun';
import { render, useState } from 'frontend-hamroun';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
render(<Counter />, document.getElementById('root'));
π§ Core Concepts
Virtual DOM
Frontend Hamroun uses a lightweight Virtual DOM implementation to efficiently update the real DOM. It only applies the minimal necessary changes by:
function App() {
const [count, setCount] = useState(0);
return (
<div>
<h1>Counter</h1>
<p>Count: {count}</p> {/* Only this text node updates */}
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
Component Model
Components are the building blocks of your UI. Each component encapsulates its own logic and rendering:
function Greeting({ name }) {
const [clicked, setClicked] = useState(false);
useEffect(() => {
document.title = `Hello, ${name}`;
return () => { document.title = 'App'; };
}, [name]);
return (
<div>
<h1>{clicked ? `Thanks, ${name}!` : `Hello, ${name}!`}</h1>
<button onClick={() => setClicked(true)}>
{clicked ? 'Clicked!' : 'Click me'}
</button>
</div>
);
}
π¨ Frontend Features
Hooks System
State Management with useState
import { useState } from 'frontend-hamroun';
function Counter() {
const [count, setCount] = useState(0);
function increment() {
setCount(count + 1);
}
function decrement() {
setCount(count - 1);
}
function double() {
setCount(prevCount => prevCount * 2);
}
return (
<div>
<h2>Count: {count}</h2>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={double}>Γ2</button>
</div>
);
}
Side Effects with useEffect
import { useState, useEffect } from 'frontend-hamroun';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
})
.catch(err => {
console.error(err);
setLoading(false);
});
return () => {
console.log('Cleaning up effect for userId:', userId);
};
}, [userId]);
if (loading) return <div>Loading...</div>;
if (!user) return <div>User not found</div>;
return (
<div>
<h1>{user.name}</h1>
<p>Email: {user.email}</p>
</div>
);
}
Performance Optimization with useMemo and useRef
import { useState, useMemo, useRef } from 'frontend-hamroun';
function ExpensiveCalculation({ items, filter }) {
const [selectedId, setSelectedId] = useState(null);
const filteredItems = useMemo(() => {
console.log('Filtering items...');
return items.filter(item => item.name.includes(filter));
}, [items, filter]);
const lastRenderTime = useRef(Date.now());
console.log(`Time since last render: ${Date.now() - lastRenderTime.current}ms`);
lastRenderTime.current = Date.now();
const listRef = useRef(null);
function scrollToTop() {
listRef.current.scrollTop = 0;
}
return (
<div>
<button onClick={scrollToTop}>Scroll to top</button>
<div ref={listRef} style={{ height: '200px', overflow: 'auto' }}>
{filteredItems.map(item => (
<div
key={item.id}
onClick={() => setSelectedId(item.id)}
style={{
background: item.id === selectedId ? 'lightblue' : 'white'
}}
>
{item.name}
</div>
))}
</div>
</div>
);
}
Context API for State Management
import { createContext, useContext, useState } from 'frontend-hamroun';
const ThemeContext = createContext('light');
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(theme === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
function ThemedButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button
onClick={toggleTheme}
style={{
background: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#333' : '#fff',
border: '1px solid #ccc',
padding: '8px 16px',
}}
>
Switch to {theme === 'light' ? 'dark' : 'light'} mode
</button>
);
}
function App() {
return (
<ThemeProvider>
<div>
<h1>Themed Application</h1>
<ThemedButton />
</div>
</ThemeProvider>
);
}
Error Boundaries with useErrorBoundary
import { useErrorBoundary } from 'frontend-hamroun';
function ErrorBoundary({ children }) {
const [error, resetError] = useErrorBoundary();
if (error) {
return (
<div className="error-boundary">
<h2>Something went wrong</h2>
<p>{error.message}</p>
<button onClick={resetError}>Try again</button>
</div>
);
}
return children;
}
function App() {
return (
<ErrorBoundary>
<UserProfile userId="123" />
</ErrorBoundary>
);
}
Batch Updates for Efficiency
Frontend Hamroun automatically batches state updates within event handlers and can manually batch other updates with batchUpdates
:
import { useState, batchUpdates } from 'frontend-hamroun';
function Form() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [isSubmitting, setSubmitting] = useState(false);
const [errors, setErrors] = useState({});
async function handleSubmit(e) {
e.preventDefault();
batchUpdates(() => {
setSubmitting(true);
setErrors({});
});
try {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, email })
});
const result = await response.json();
batchUpdates(() => {
setSubmitting(false);
setName('');
setEmail('');
});
} catch (error) {
batchUpdates(() => {
setSubmitting(false);
setErrors({ submit: error.message });
});
}
}
return (
<form onSubmit={handleSubmit}>
{/* Form fields */}
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
</form>
);
}
π₯οΈ Backend Features
Express Server Integration
Server Setup
import { server } from 'frontend-hamroun/server';
const app = await server.createServer({
port: 3000,
apiDir: './api',
pagesDir: './pages',
staticDir: './public',
db: {
url: process.env.DATABASE_URL,
type: 'mongodb'
},
auth: {
secret: process.env.JWT_SECRET,
expiresIn: '7d'
}
});
await app.start();
console.log('Server running at http://localhost:3000');
File-Based API Routing
API Routes
export async function get(req, res) {
const users = await req.db.collection('users').find().toArray();
res.json(users);
}
export async function post(req, res) {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({ error: 'Name and email are required' });
}
const result = await req.db.collection('users').insertOne({
name,
email,
createdAt: new Date()
});
res.status(201).json({ id: result.insertedId });
}
export async function get(req, res) {
const { id } = req.params;
try {
const user = await req.db.collection('users').findOne({
_id: new ObjectId(id)
});
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
} catch (error) {
res.status(500).json({ error: 'Server error' });
}
}
export async function put(req, res) {
const { id } = req.params;
const { name, email } = req.body;
await req.db.collection('users').updateOne(
{ _id: new ObjectId(id) },
{ $set: { name, email, updatedAt: new Date() } }
);
res.json({ success: true });
}
export async function del(req, res) {
const { id } = req.params;
await req.db.collection('users').deleteOne({
_id: new ObjectId(id)
});
res.status(204).end();
}
Database Integration
MongoDB Example
export async function get(req, res) {
const posts = await req.db.collection('posts')
.aggregate([
{ $match: { published: true } },
{ $sort: { createdAt: -1 } },
{ $limit: 10 },
{ $lookup: {
from: 'users',
localField: 'authorId',
foreignField: '_id',
as: 'author'
}},
{ $unwind: '$author' },
{ $project: {
title: 1,
content: 1,
createdAt: 1,
'author.name': 1,
'author.email': 1
}}
])
.toArray();
res.json(posts);
}
MySQL Example
export async function get(req, res) {
const [products] = await req.db.execute(`
SELECT
p.id,
p.name,
p.price,
c.name as categoryName
FROM
products p
JOIN
categories c ON p.category_id = c.id
WHERE
p.active = ?
ORDER BY
p.created_at DESC
LIMIT 20
`, [true]);
res.json(products);
}
PostgreSQL Example
export async function get(req, res) {
const result = await req.db.query(`
WITH monthly_sales AS (
SELECT
date_trunc('month', order_date) as month,
SUM(total_amount) as revenue
FROM
orders
WHERE
order_date > NOW() - INTERVAL '1 year'
GROUP BY
date_trunc('month', order_date)
)
SELECT
month,
revenue,
lag(revenue) OVER (ORDER BY month) as prev_month_revenue,
round((revenue - lag(revenue) OVER (ORDER BY month)) /
lag(revenue) OVER (ORDER BY month) * 100, 2) as growth_percent
FROM
monthly_sales
ORDER BY
month
`);
res.json(result.rows);
}
Authentication System
JWT Authentication
export async function post(req, res) {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({ error: 'Email and password are required' });
}
try {
const user = await req.db.collection('users').findOne({ email });
if (!user || !await req.auth.comparePasswords(password, user.password)) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const token = req.auth.generateToken({
id: user._id,
name: user.name,
email: user.email,
roles: user.roles || ['user']
});
res.json({
token,
user: {
id: user._id,
name: user.name,
email: user.email,
roles: user.roles || ['user']
}
});
} catch (error) {
res.status(500).json({ error: 'Authentication failed' });
}
}
export async function post(req, res) {
const { name, email, password } = req.body;
if (!name || !email || !password) {
return res.status(400).json({
error: 'Name, email, and password are required'
});
}
try {
const existingUser = await req.db.collection('users').findOne({ email });
if (existingUser) {
return res.status(409).json({ error: 'Email already in use' });
}
const hashedPassword = await req.auth.hashPassword(password);
const result = await req.db.collection('users').insertOne({
name,
email,
password: hashedPassword,
roles: ['user'],
createdAt: new Date()
});
res.status(201).json({
id: result.insertedId,
name,
email,
roles: ['user']
});
} catch (error) {
res.status(500).json({ error: 'Registration failed' });
}
}
export default function requireAuth(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Authentication required' });
}
try {
const decoded = req.auth.verifyToken(token);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid or expired token' });
}
}
import requireAuth from '../middleware/requireAuth.js';
export const middleware = [requireAuth];
export async function get(req, res) {
const { id } = req.user;
const profile = await req.db.collection('users').findOne(
{ _id: new ObjectId(id) },
{ projection: { password: 0 } }
);
res.json(profile);
}
π Server-Side Rendering
Frontend Hamroun provides built-in server-side rendering capabilities to improve performance and SEO:
Server-Side Rendering Setup
import express from 'express';
import { renderToString } from 'frontend-hamroun/ssr';
import App from './App';
const app = express();
app.use(express.static('public'));
app.get('*', async (req, res) => {
const html = await renderToString(<App url={req.url} />);
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>My SSR App</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<div id="root">${html}</div>
<script src="/bundle.js"></script>
</body>
</html>
`);
});
app.listen(3000, () => {
console.log('Server running at http://localhost:3000');
});
import { hydrate } from 'frontend-hamroun';
import App from './App';
hydrate(<App url={window.location.pathname} />, document.getElementById('root'));
π οΈ CLI Tools
Frontend Hamroun includes a powerful CLI for scaffolding projects, components, and API routes:
Project Creation
npx frontend-hamroun create my-app
npx frontend-hamroun create my-app --template fullstack-app
Available templates:
basic-app
: Minimal client-side setup
ssr-template
: Server-side rendering with hydration
fullstack-app
: Complete solution with frontend, backend, auth, and DB
Component Generation
npx frontend-hamroun add:component Button
npx frontend-hamroun add:component UserProfile --typescript
npx frontend-hamroun add:component Sidebar --path=src/layout --hooks=useState,useEffect
API Route Generation
npx frontend-hamroun add:api products
npx frontend-hamroun add:api orders --methods=get,post --auth
π§© Advanced Usage
Custom Hooks
import { useState, useEffect } from 'frontend-hamroun';
function useFetch(url, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
setError(null);
fetch(url, options)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error ${response.status}`);
}
return response.json();
})
.then(json => {
setData(json);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
});
}, [url]);
return { data, loading, error };
}
function UserList() {
const { data, loading, error } = useFetch('/api/users');
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
Performance Optimization Techniques
import { useMemo, useState, useRef, batchUpdates } from 'frontend-hamroun';
function OptimizedList({ items }) {
const [filter, setFilter] = useState('');
const [sortOrder, setSortOrder] = useState('asc');
const prevItems = useRef(items);
const processedItems = useMemo(() => {
console.log('Processing items...');
let result = [...items];
if (filter) {
result = result.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
}
result.sort((a, b) => {
const comparison = a.name.localeCompare(b.name);
return sortOrder === 'asc' ? comparison : -comparison;
});
return result;
}, [items, filter, sortOrder]);
if (items !== prevItems.current) {
console.log('Items array reference changed');
prevItems.current = items;
}
const toggleSortAndClear = () => {
batchUpdates(() => {
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc');
setFilter('');
});
};
return (
<div>
<div>
<input
type="text"
value={filter}
onChange={e => setFilter(e.target.value)}
placeholder="Filter items..."
/>
<button onClick={toggleSortAndClear}>
Toggle Sort ({sortOrder})
</button>
</div>
{/* Using key for efficient list rendering */}
<ul>
{processedItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
π TypeScript Support
Frontend Hamroun is built with TypeScript and comes with complete type definitions:
Type-Safe Component Example
import { useState, useEffect } from 'frontend-hamroun';
interface UserProfileProps {
id: number;
name: string;
email: string;
role: 'admin' | 'user' | 'guest';
onStatusChange?: (id: number, active: boolean) => void;
}
interface UserProfileState {
isActive: boolean;
isEditing: boolean;
formData: {
name: string;
email: string;
};
}
function UserProfile({
id,
name,
email,
role,
onStatusChange
}: UserProfileProps) {
const [state, setState] = useState<UserProfileState>({
isActive: true,
isEditing: false,
formData: {
name,
email
}
});
const toggleStatus = (): void => {
const newStatus = !state.isActive;
setState(prev => ({
...prev,
isActive: newStatus
}));
if (onStatusChange) {
onStatusChange(id, newStatus);
}
};
const formRef = useRef<HTMLFormElement>(null);
return (
<div className="user-profile">
<h2>{name}</h2>
<p>Email: {email}</p>
<p>Role: {role}</p>
<p>Status: {state.isActive ? 'Active' : 'Inactive'}</p>
<button onClick={toggleStatus}>
{state.isActive ? 'Deactivate' : 'Activate'}
</button>
{state.isEditing ? (
<form ref={formRef}>
{/* Form fields */}
</form>
) : (
<button onClick={() => setState(prev => ({
...prev,
isEditing: true
}))}>
Edit
</button>
)}
</div>
);
}
const App = () => (
<div>
<UserProfile
id={1}
name="John Doe"
email="john@example.com"
role="admin"
onStatusChange={(id, active) => {
console.log(`User ${id} status changed to ${active}`);
}}
/>
{/* This would cause TypeScript errors */}
{/*
<UserProfile
id="1" // Error: string is not assignable to number
name="Jane Doe"
role="manager" // Error: 'manager' is not assignable
/>
*/}
</div>
);
Type-Safe API Routes
export interface User {
id?: string;
name: string;
email: string;
password?: string;
role: 'admin' | 'user';
createdAt?: Date;
updatedAt?: Date;
}
import type { User } from '../../types';
export async function get(req, res) {
const { id } = req.params;
try {
const user = await req.db.collection('users').findOne({
_id: new ObjectId(id)
}) as User;
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
const { password, ...safeUser } = user;
res.json(safeUser);
} catch (error) {
res.status(500).json({ error: 'Server error' });
}
}
export async function put(req, res) {
const { id } = req.params;
const { name, email, role } = req.body as Partial<User>;
if (role && !['admin', 'user'].includes(role)) {
return res.status(400).json({ error: 'Invalid role' });
}
try {
await req.db.collection('users').updateOne(
{ _id: new ObjectId(id) },
{
$set: {
...(name && { name }),
...(email && { email }),
...(role && { role }),
updatedAt: new Date()
}
}
);
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: 'Server error' });
}
}
π Browser Compatibility
Frontend Hamroun supports all modern browsers out of the box:
- Chrome (latest 2 versions)
- Firefox (latest 2 versions)
- Safari (latest 2 versions)
- Edge (latest 2 versions)
For older browsers like IE11, you'll need:
- Appropriate polyfills
- Transpilation to ES5
- CSS compatibility work
β Frequently Asked Questions
How does Frontend Hamroun compare to React?
Frontend Hamroun offers a React-like API but with additional built-in features:
- Smaller bundle size (~5KB vs. 42KB+)
- Integrated backend capabilities
- Built-in server-side rendering
- Database integrations
- Authentication system
For React developers, the learning curve is minimal.
Can I use it with existing React components?
While Frontend Hamroun's API is similar to React's, they have different internal implementations. You can:
- Port React components to Frontend Hamroun with minimal changes
- Use React components in dedicated sections with a compatibility layer
- Share non-UI code and logic between both frameworks
Does it support static site generation (SSG)?
Yes, Frontend Hamroun provides tools for static site generation. Use the renderToString
API to pre-render pages at build time.
π License
MIT Β© Hamroun
β¬οΈ Back to top