Lumina is a lightweight and efficient framework for building full-stack single-page applications (SPAs) with ease.
To create a new Lumina project, run:
npx create-lumina@latest my-lumina
-
my-lumina
is the folder name where the project will be created. -
You can omit the folder name and use:
npx create-lumina@latest
This will create a default folder named
my-lumina-app
. -
To install Lumina in the current directory, use:
npx create-lumina@latest .
After the installation is complete, when you see:
Happy coding with Lumina! 🎉
Run the following command to start the development server:
npm run dev
Your Lumina app will be available at:
http://localhost:3000/
Components in Lumina are JavaScript functions that return HTML. All pages are created with JS and inserted into a div with id="lumina__root"
in the HTML file:
<div id="lumina__root">
<!-- Content will be dynamically loaded here -->
</div>
Basic component example (HomePage.js):
async function HomePage() {
// set page title
const title = 'HomePage | LUMINA';
document.title = title;
// other JavaScript Logics
return `
<div>
Home page of Lumina
</div>
`;
}
export default HomePage;
Component naming rules:
- File name must match function name
- Start with uppercase letter
- Must use
export default
For components that need to fetch data:
async function TestPage() {
// set page title
const title = 'Test | LUMINA';
document.title = title;
const res = await fetch(
`http://localhost:3000/api/user?api_token=tk_live_abc123`
);
const data = await res.json();
console.log('data: ', data);
return `
<div class="text-center text-5xl font-bold mb-10">
hello world!
</div>
`;
}
export default TestPage;
Note: Always include ?api_token=tk_live_abc123
in API fetch requests due to the apiTokenAuth middleware.
You can create reusable components in the components/module
directory. For organization, group related components in subdirectories:
Example of using nested components:
import Comments from '../module/home/Comments.js';
import FAQ from '../module/home/FAQ.js';
import Price from '../module/home/Price.js';
async function HomePage() {
const title = 'Home | LUMINA';
document.title = title;
return `
<div class="container mx-auto px-4 py-16">
<section class="text-center mb-16">
<h1 class="text-5xl font-bold text-gray-800 mb-4">Create Stunning SPAs with LUMINA</h1>
<p class="text-xl text-gray-600 mb-8">The next-generation SPA maker for modern web applications</p>
<a href="#" class="bg-indigo-600 text-white px-8 py-3 rounded-full text-lg font-semibold hover:bg-indigo-700 transition">Get Started</a>
</section>
<!-- using home page components -->
${Comments()}
${Price()}
${FAQ()}
<section class="text-center">
<h2 class="text-3xl font-bold text-gray-800 mb-4">Ready to Illuminate Your Web Projects?</h2>
<p class="text-xl text-gray-600 mb-8">Join thousands of developers who trust LUMINA for their SPA needs</p>
<a href="https://github.com/kiarashAlizadeh/create-lumina" class="bg-indigo-600 text-white px-8 py-3 rounded-full text-lg font-semibold hover:bg-indigo-700 transition">Start Coding</a>
</section>
</div>
`;
}
export default HomePage;
Example nested component (Comments.js):
function Comments() {
return `
<!-- Testimonial Section -->
<section class="mb-16">
<h2 class="text-3xl font-bold text-gray-800 mb-8 text-center">What Our Users Say</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div class="bg-white p-6 rounded-lg shadow-md">
<p class="text-gray-600 mb-4">"LUMINA has revolutionized the way we build SPAs. It's fast, intuitive, and the results are stunning!"</p>
<p class="font-semibold text-gray-800">- Sarah Johnson, Frontend Developer</p>
</div>
<div class="bg-white p-6 rounded-lg shadow-md">
<p class="text-gray-600 mb-4">"I've never been able to create such beautiful and responsive SPAs so quickly. LUMINA is a game-changer!"</p>
<p class="font-semibold text-gray-800">- Michael Chen, UX Designer</p>
</div>
</div>
</section>
`;
}
export default Comments;
Define routes in routes.js
:
const mainRoutes = [
{ path: '/', component: 'HomePage', css: ['home'] },
{ path: '/test', component: 'TestPage', css: ['test', 'testSass'] },
];
const notFoundRoute = {
path: '/404',
component: 'NotFoundPage',
css: ['notFound'],
};
export { mainRoutes, notFoundRoute };
Route configuration:
path
: URL path after domaincomponent
: Component name (must exist in components/pages)css
: Optional array of CSS files to link
Lumina automatically handles undefined routes and displays the 404 page. If a user navigates to a non-existent route, they will be redirected to the designated NotFoundPage
.
You can customize the 404 page component and styles to match your branding by modifying NotFoundPage
and its associated styles in assets/css/notFound.css
.
The 404 page ensures a better user experience by informing users that the requested page does not exist, preventing confusion and improving navigation.
Main layout configuration in components/layout/layout.js
:
import Header from './Header.js';
import Footer from './Footer.js';
async function Layout(PageContent) {
const HeaderContent = await Header();
const FooterContent = await Footer();
return `${HeaderContent}<main>${PageContent}</main>${FooterContent}`;
}
export default Layout;
Header and footer components are in:
components/layout/header.js
components/layout/footer.js
It's recommended to put header and footer styles in assets/css/index.css
to avoid duplicate styles.
Lumina includes a default loading screen that appears while content is loading, especially useful during API fetches. Customize the loader in:
- HTML:
components/layout/loader.js
- CSS:
assets/css/index.css
Lumina supports multiple styling approaches:
-
Global CSS: Use
assets/css/index.css
for site-wide styles -
Page-specific CSS: Create files in
assets/css/pageStyle/
and link them in routes.js -
Sass: Write in
assets/sass/
- automatically compiled to CSS -
Tailwind CSS V4.0: Pre-configured and ready to use:
<div class="bg-amber-500">Lumina is using TailwindCSS!</div>
Note: The css
property in routes is optional, especially if you're using Tailwind CSS.
Use regular <a>
tags for navigation. Lumina handles route changes without page refreshes:
<a href="/">Home</a>
Create dynamic URLs using hash fragments:
<a href="/test#7775">Test Page</a>
Access the dynamic ID in your component:
async function TestPage() {
const title = 'Test | LUMINA';
document.title = title;
// dynamic id in url
const id = window.location.toString().split('#')[1];
return `
<div>
test Id: ${id}
</div>
`;
}
HTTP Methods:
GET
: Retrieve dataPOST
: Create new dataPUT
: Update entire resourcePATCH
: Partial updateDELETE
: Remove data
HTTP Status Codes:
- 200: OK (Success)
- 201: Created
- 400: Bad Request
- 401: Unauthorized
- 403: Forbidden
- 404: Not Found
- 500: Internal Server Error
For complete status code reference, visit MDN HTTP Status.
Lumina uses MongoDB with Mongoose by default. Database connection is configured in lumina.config.js
:
async function DBConnect() {
try {
const DATABASE_URL = process.env.DATABASE_URL;
if (!DATABASE_URL) {
console.error('DATABASE_URL is not defined in .env file');
return;
}
// Connecting to the database
await mongoose.connect(DATABASE_URL);
console.log('Successfully connected to the Database!');
} catch (error) {
console.error('Failed to connect to the Database:', error);
}
}
Define models in api/models/
with uppercase naming convention:
import mongoose from 'mongoose';
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
pass: {
type: String,
required: true,
},
role: {
type: String,
default: 'USER',
},
createdAt: {
type: Date,
default: Date.now(),
immutable: true,
},
});
export default mongoose.model('User', userSchema);
Create API routes in api/routes/
. The filename determines the endpoint (e.g., user.js
creates /api/user
):
import express from 'express';
const router = express.Router();
import User from '../models/User.js';
// Getting all
router.get('/', async (req, res) => {
try {
const users = await User.find();
res.json(users);
} catch (err) {
res.status(500).json({ message: err.message });
}
});
// Getting One
router.get('/:id', async (req, res) => {
try {
const user = await User.findById(req.params.id);
if (!user) {
return res.status(404).json({ message: 'user not found' });
}
res.json(user);
} catch (err) {
res.status(500).json({ message: err.message });
}
});
// Creating one
router.post('/', async (req, res) => {
try {
const {
name,
familyName,
userName,
email,
birthDate,
pass,
gender,
phone,
favoriteTeam,
} = req.body;
const newUser = await User.create({
name,
familyName,
userName,
email,
birthDate,
pass,
gender,
phone,
favoriteTeam,
});
res.status(201).json({ message: 'user created' });
} catch (err) {
res.status(400).json({ message: err.message });
}
});
// Updating One
router.patch('/:id', async (req, res) => {
try {
const user = await User.findById(req.params.id);
if (!user) {
return res.status(404).json({ message: 'user not found' });
}
// Update fields based on request body
if (req.body.name) user.name = req.body.name;
if (req.body.familyName) user.familyName = req.body.familyName;
if (req.body.userName) user.userName = req.body.userName;
if (req.body.email) user.email = req.body.email;
await user.save();
res.status(201).json({ message: 'user Updated' });
} catch (err) {
res.status(400).json({ message: err.message });
}
});
// Deleting One
router.delete('/:id', async (req, res) => {
try {
const user = await User.findByIdAndDelete(req.params.id);
if (!user) {
return res.status(404).json({ message: 'user not found' });
}
res.json({ message: 'Deleted User' });
} catch (err) {
res.status(500).json({ message: err.message });
}
});
export default router;
Middlewares are functions that run between request and response. They can:
- Authenticate requests
- Log information
- Modify requests/responses
- Handle errors
Create middlewares in api/middleware/
with numbered prefixes for execution order(e.g., 02-loggerTest.js
runs after 01-apiTokenAuth.js
):
export const requestLogger = () => (req, res, next) => {
console.log('< -- middleware logger 01 -- >');
next();
};
API Token Authentication:
Configure tokens in lumina.config.js
:
const API_TOKENS = new Map([
[
'tk_live_abc123',
{
name: 'Production App',
permissions: ['read', 'write'],
isActive: true,
rateLimit: 1000, // requests per hour
},
],
[
'tk_test_xyz789',
{
name: 'Test App',
permissions: ['read'],
isActive: true,
rateLimit: 100,
},
],
]);
Store sensitive configuration in .env
:
DATABASE_URL="mongodb://127.0.0.1:27017/lumina"
The .env
file should be in the project root and is used for:
- Database connection strings
- Server Port
- API keys
- Sensitive configuration
- Environment-specific settings
Never commit the .env
file to version control.
Configuring the Port
By default, Lumina runs on port 3000
. You can change this by setting the PORT
variable in your .env
file or modifying the lumina.config.js
file:
// lumina port
const port = process.env.PORT || 3000;
This allows you to customize the server port based on your environment.
To deploy the application on a server where Node.js is already installed, follow these steps:
-
Upload the project files to the server. You can use FTP, SCP, or any other method.
-
Navigate to the project directory in the terminal:
cd /path/to/your/project
-
Install dependencies:
npm install
-
Start the application:
npm start
The application will run on port
3000
by default or the port defined in the.env
file (e.g.,PORT=3001
). -
Connect to a real database (if applicable) by setting
DATABASE_URL
in the.env
file. -
Access the application:
- If no domain is set, open
http://your-server-ip:3000
in a browser. - If a domain is connected to the server, simply type your domain in the browser.
- If no domain is set, open
Your application is now live! 🚀
Happy coding with Lumina! ❤️