
Security News
ESLint Adds Official Support for Linting HTML
ESLint now supports HTML linting with 48 new rules, expanding its language plugin system to cover more of the modern web development stack.
frontend-hamroun
Advanced tools
A lightweight full-stack JavaScript framework with Virtual DOM and hooks implementation
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.
# Using npm
npm install frontend-hamroun
# Using yarn
yarn add frontend-hamroun
# Using pnpm
pnpm add frontend-hamroun
# Using npx
npx create-frontend-app my-app
# Or with the frontend-hamroun CLI
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.
# Install the package
npm install frontend-hamroun
# Import and use in your code
import { render, useState } from 'frontend-hamroun';
// Create a simple app
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'));
Frontend Hamroun uses a lightweight Virtual DOM implementation to efficiently update the real DOM. It only applies the minimal necessary changes by:
// Virtual DOM diffing occurs automatically
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>
);
}
Components are the building blocks of your UI. Each component encapsulates its own logic and rendering:
// Function components with hooks
function Greeting({ name }) {
// State management
const [clicked, setClicked] = useState(false);
// Side effects
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>
);
}
import { useState } from 'frontend-hamroun';
function Counter() {
const [count, setCount] = useState(0);
function increment() {
setCount(count + 1);
}
function decrement() {
setCount(count - 1);
}
// Functional updates for derived state
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>
);
}
import { useState, useEffect } from 'frontend-hamroun';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Reset state when userId changes
setLoading(true);
// Fetch user data
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
})
.catch(err => {
console.error(err);
setLoading(false);
});
// Cleanup function runs on component unmount or before effect re-runs
return () => {
// Cancel any pending requests or subscriptions
console.log('Cleaning up effect for userId:', userId);
};
}, [userId]); // Only re-run when userId changes
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>
);
}
import { useState, useMemo, useRef } from 'frontend-hamroun';
function ExpensiveCalculation({ items, filter }) {
const [selectedId, setSelectedId] = useState(null);
// Cache expensive calculation results, only recalculate when dependencies change
const filteredItems = useMemo(() => {
console.log('Filtering items...');
return items.filter(item => item.name.includes(filter));
}, [items, filter]);
// Create a persistent reference that doesn't trigger re-renders
const lastRenderTime = useRef(Date.now());
console.log(`Time since last render: ${Date.now() - lastRenderTime.current}ms`);
lastRenderTime.current = Date.now();
// DOM element references
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>
);
}
import { createContext, useContext, useState } from 'frontend-hamroun';
// Create a context with default value
const ThemeContext = createContext('light');
// Provider component to supply context value
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(theme === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// Consumer component using the context value
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>
);
}
// Usage in application
function App() {
return (
<ThemeProvider>
<div>
<h1>Themed Application</h1>
<ThemedButton />
</div>
</ThemeProvider>
);
}
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;
}
// Usage
function App() {
return (
<ErrorBoundary>
<UserProfile userId="123" />
</ErrorBoundary>
);
}
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();
// Group multiple state updates into a single render
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>
);
}
import { server } from 'frontend-hamroun/server';
const app = await server.createServer({
port: 3000,
apiDir: './api', // Directory for API routes
pagesDir: './pages', // Directory for page components
staticDir: './public', // Directory for static files
// Database configuration
db: {
url: process.env.DATABASE_URL,
type: 'mongodb' // mongodb, mysql, or postgres
},
// Authentication configuration
auth: {
secret: process.env.JWT_SECRET,
expiresIn: '7d' // Token expiration time
}
});
await app.start();
console.log('Server running at http://localhost:3000');
// api/users.js (automatically maps to /api/users)
export async function get(req, res) {
// GET /api/users - List all users
const users = await req.db.collection('users').find().toArray();
res.json(users);
}
export async function post(req, res) {
// POST /api/users - Create a new user
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 });
}
// api/users/[id].js (automatically maps to /api/users/:id)
export async function get(req, res) {
// GET /api/users/:id - Get user by ID
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) {
// PUT /api/users/:id - Update user
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) {
// DELETE /api/users/:id - Delete user
// Note: 'delete' is a reserved word, so we use 'del'
const { id } = req.params;
await req.db.collection('users').deleteOne({
_id: new ObjectId(id)
});
res.status(204).end();
}
// api/posts.js
export async function get(req, res) {
// Complex MongoDB aggregation
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);
}
// api/products.js
export async function get(req, res) {
// Complex SQL query with joins
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);
}
// api/analytics.js
export async function get(req, res) {
// Advanced PostgreSQL features
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);
}
// api/auth/login.js
export async function post(req, res) {
const { email, password } = req.body;
// Validate input
if (!email || !password) {
return res.status(400).json({ error: 'Email and password are required' });
}
try {
// Find user by email
const user = await req.db.collection('users').findOne({ email });
// Check if user exists and password is correct
if (!user || !await req.auth.comparePasswords(password, user.password)) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Generate JWT token
const token = req.auth.generateToken({
id: user._id,
name: user.name,
email: user.email,
roles: user.roles || ['user']
});
// Return token and user info (excluding sensitive data)
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' });
}
}
// api/auth/register.js
export async function post(req, res) {
const { name, email, password } = req.body;
// Validate input
if (!name || !email || !password) {
return res.status(400).json({
error: 'Name, email, and password are required'
});
}
try {
// Check if user already exists
const existingUser = await req.db.collection('users').findOne({ email });
if (existingUser) {
return res.status(409).json({ error: 'Email already in use' });
}
// Hash password
const hashedPassword = await req.auth.hashPassword(password);
// Create user
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' });
}
}
// middleware/requireAuth.js - Protected route middleware
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 {
// Verify token
const decoded = req.auth.verifyToken(token);
req.user = decoded; // Attach user to request
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid or expired token' });
}
}
// api/profile.js - Protected route example
import requireAuth from '../middleware/requireAuth.js';
export const middleware = [requireAuth];
export async function get(req, res) {
// req.user is available from requireAuth middleware
const { id } = req.user;
const profile = await req.db.collection('users').findOne(
{ _id: new ObjectId(id) },
{ projection: { password: 0 } } // Exclude password
);
res.json(profile);
}
Frontend Hamroun provides built-in server-side rendering capabilities to improve performance and SEO:
// server.js
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) => {
// Render app to string
const html = await renderToString(<App url={req.url} />);
// Send complete HTML document
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');
});
// client.js (for hydration)
import { hydrate } from 'frontend-hamroun';
import App from './App';
// Hydrate the app in the browser
hydrate(<App url={window.location.pathname} />, document.getElementById('root'));
Frontend Hamroun includes a powerful CLI for scaffolding projects, components, and API routes:
# Create a new project with interactive prompts
npx frontend-hamroun create my-app
# Create with specific template
npx frontend-hamroun create my-app --template fullstack-app
Available templates:
basic-app
: Minimal client-side setupssr-template
: Server-side rendering with hydrationfullstack-app
: Complete solution with frontend, backend, auth, and DB# Generate a new component
npx frontend-hamroun add:component Button
# Generate a TypeScript component
npx frontend-hamroun add:component UserProfile --typescript
# Specify path and hooks
npx frontend-hamroun add:component Sidebar --path=src/layout --hooks=useState,useEffect
# Generate API route
npx frontend-hamroun add:api products
# Specify HTTP methods and auth requirement
npx frontend-hamroun add:api orders --methods=get,post --auth
import { useState, useEffect } from 'frontend-hamroun';
// Custom hook for fetching data
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 };
}
// Usage
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>
);
}
import { useMemo, useState, useRef, batchUpdates } from 'frontend-hamroun';
function OptimizedList({ items }) {
const [filter, setFilter] = useState('');
const [sortOrder, setSortOrder] = useState('asc');
const prevItems = useRef(items);
// Memoize expensive calculations
const processedItems = useMemo(() => {
console.log('Processing items...');
let result = [...items];
// Apply filter
if (filter) {
result = result.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
}
// Apply sorting
result.sort((a, b) => {
const comparison = a.name.localeCompare(b.name);
return sortOrder === 'asc' ? comparison : -comparison;
});
return result;
}, [items, filter, sortOrder]);
// Check for changed items with more control than dependency arrays
if (items !== prevItems.current) {
console.log('Items array reference changed');
prevItems.current = items;
}
// Batch multiple state updates together
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>
);
}
Frontend Hamroun is built with TypeScript and comes with complete type definitions:
import { useState, useEffect } from 'frontend-hamroun';
// Define props interface
interface UserProfileProps {
id: number;
name: string;
email: string;
role: 'admin' | 'user' | 'guest';
onStatusChange?: (id: number, active: boolean) => void;
}
// Define state interface
interface UserProfileState {
isActive: boolean;
isEditing: boolean;
formData: {
name: string;
email: string;
};
}
function UserProfile({
id,
name,
email,
role,
onStatusChange
}: UserProfileProps) {
// Type-safe state
const [state, setState] = useState<UserProfileState>({
isActive: true,
isEditing: false,
formData: {
name,
email
}
});
// Type-safe event handlers
const toggleStatus = (): void => {
const newStatus = !state.isActive;
setState(prev => ({
...prev,
isActive: newStatus
}));
if (onStatusChange) {
onStatusChange(id, newStatus);
}
};
// Type-safe refs
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>
);
}
// Usage with type checking
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>
);
// types.ts
export interface User {
id?: string;
name: string;
email: string;
password?: string;
role: 'admin' | 'user';
createdAt?: Date;
updatedAt?: Date;
}
// api/users/[id].ts
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' });
}
// Omit sensitive data
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>;
// Validate that role is a valid enum value
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' });
}
}
Frontend Hamroun supports all modern browsers out of the box:
For older browsers like IE11, you'll need:
Frontend Hamroun offers a React-like API but with additional built-in features:
For React developers, the learning curve is minimal.
While Frontend Hamroun's API is similar to React's, they have different internal implementations. You can:
Yes, Frontend Hamroun provides tools for static site generation. Use the renderToString
API to pre-render pages at build time.
MIT Ā© Hamroun
FAQs
A lightweight full-stack JavaScript framework
The npm package frontend-hamroun receives a total of 1,307 weekly downloads. As such, frontend-hamroun popularity was classified as popular.
We found that frontend-hamroun demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.Ā It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
ESLint now supports HTML linting with 48 new rules, expanding its language plugin system to cover more of the modern web development stack.
Security News
CISA is discontinuing official RSS support for KEV and cybersecurity alerts, shifting updates to email and social media, disrupting automation workflows.
Security News
The MCP community is launching an official registry to standardize AI tool discovery and let agents dynamically find and install MCP servers.