Complete Web Project: MVC Architecture with the Native Stack
Previous: Console project. Next: Controllers and HTTP. Technical reference: View engine, Native server, GranDB models.
What Will You Build Here?
A web application is a program that runs on a server listening to network requests (usually from a browser or mobile application) and responds with interactive HTML pages or data in JSON format.
Unlike other languages where you need to install complex external servers (such as Apache, Nginx, or heavy Node.js packages), Joss includes its own high-performance HTTP server, dynamic router, and HTML template engine.
In this guided tutorial you will build your first web application based on the MVC (Model - View - Controller) architectural pattern:
1. Create the project structure using the joss new template generator (joss new web).
2. Understand the flow of a web request in Joss.
3. Learn the joss make:<componente> code generator system to create project pieces without writing them from scratch.
4. Configure the port in env.joss and define a route with dynamic parameters in routes.joss.
5. Generate and program a controller in app/controllers/ with joss make:controller.
6. Generate and design a secure HTML view in app/views/ with joss make:view and automatic escaping against XSS injections.
7. Start the server with joss server start and test the application in the browser.
1. The Flow of a Web Request in Joss
When a user types an address into their browser, the following sequence occurs:
flowchart TD
B["1. Navegador web — Solicita GET http://127.0.0.1:8080/saludo/Ana"] --> S["2. Servidor HTTP nativo de Joss (Alta concurrencia)"]
S --> R["3. Router (routes.joss) — Coincide '/saludo/{nombre}' y extrae $nombre = 'Ana'"]
R --> C["4. Controlador (app/controllers/SaludoController.joss) — Ejecuta SaludoController@show($nombre)"]
C --> V["5. Motor de Vistas (app/views/saludo.joss.html) — Interpola variables y escapa HTML"]
V --> H["6. Respuesta HTTP — Devuelve el HTML renderizado al navegador"]
2. Generate the Web Project and Learn the joss make:* Commands
Open your terminal and run the official generator (you can type joss new saludo_web or explicitly joss new web saludo_web):
joss new web saludo_web
cd saludo_web
This command will create the standard structure of a Joss web application:
- main.joss: Entry point of the project.
- env.joss: Configuration variables (port, database, secret keys).
- routes.joss and api.joss: The map of web URLs and API endpoints of the application.
- app/controllers/: Where controllers reside (organized into web/, auth/, api/).
- app/models/: Database models with GranDB.
- app/views/: Dynamic HTML templates (.joss.html).
- public/: Direct static files (CSS stylesheets, JS scripts, images).
The joss make:<componente> Generator Design
Instead of creating files manually one by one, Joss provides the joss make:<componente> command family, which automatically generates classes and templates in the right folder following framework conventions:
flowchart LR
CLI["Terminal: joss make:<componente>"] --> CTRL["joss make:controller Nombre → app/controllers/Nombre.joss"]
CLI --> VIEW["joss make:view nombre → app/views/nombre.joss.html"]
CLI --> MOD["joss make:model Nombre → app/models/Nombre.joss"]
CLI --> MID["joss make:middleware Nombre → app/middleware/Nombre.joss"]
CLI --> MIG["joss make:migration nombre → app/database/migrations/..."]
CLI --> MVC["joss make:mvc Nombre → Modelo + Controlador + Vista"]
CLI --> CRUD["joss make:crud tabla → CRUD completo + Rutas + Vistas"]
| Command | What does it automatically create? | Usage example |
|---|---|---|
joss make:controller <Nombre> |
A controller in app/controllers/<Nombre>.joss (supports subfolders such as web/SaludoController) |
joss make:controller SaludoController |
joss make:view <nombre> |
An HTML template in app/views/<nombre>.joss.html (supports subfolders such as usuarios/perfil) |
joss make:view saludo |
joss make:model <Nombre> |
A GranDB model in app/models/<Nombre>.joss connected to its pluralized table |
joss make:model Producto |
joss make:middleware <Nombre> |
An HTTP middleware in app/middleware/<Nombre>.joss |
joss make:middleware VerificarSesion |
joss make:mvc <Nombre> |
The complete triad at once: Model + Controller (<Nombre>Controller) + View (<nombre>/index) |
joss make:mvc Producto |
joss make:migration <nombre> |
A timestamped database migration in app/database/migrations/ |
joss make:migration create_productos |
joss make:crud <tabla> |
Inspects the database table and generates Model, Controller, Tailwind Views, and Routes (reverted with joss remove:crud <tabla>) |
joss make:crud productos |
3. Configure the Server Port
Open the env.joss file at the root of the project. By default the template generates PORT="80". To develop locally without administrator privileges, change it to a free port such as "8080":
PORT="8080"
APP_NAME="Mi Web Joss"
[!NOTE]
env.jossis an environment configuration file that Joss loads automatically on startup; it is not an executable script to run withjoss run env.joss.
4. Define the Dynamic Route
Open the routes.joss file and add the following definition:
Router::get("/saludo/{nombre}", "SaludoController@show")
What Does This Line Mean?
Router::get(...): Indicates that we will respond to HTTP requests of typeGET(the ones made by the browser when opening a link)."/saludo/{nombre}": The URL pattern.{nombre}is a dynamic segment: any text the user types into that position of the URL will be captured."SaludoController@show": The target handler. Tells the router to instantiate theSaludoControllerclass and execute itsshowmethod.
5. Create the Controller with joss make:controller
In your terminal, inside the saludo_web folder, run the controller generator:
joss make:controller SaludoController
You will see the confirmation in the console:
Created: app/controllers/SaludoController.joss
Joss will have created the app/controllers/SaludoController.joss file with an initial structure ready to use:
public class SaludoController {
public func index(): mixed {
return view("saludo/index")
}
}
Now open app/controllers/SaludoController.joss and add or replace the method with show(string $nombre) to receive the parameter from our route:
public class SaludoController {
public func show(string $nombre) {
var $nombreLimpio = trim($nombre)
return view("saludo", {"nombre": $nombreLimpio})
}
}
Anatomy of the Controller:
public class SaludoController: Public class that Joss automatically discovers insideapp/without needing animport.public func show(string $nombre): The method receives the dynamic parameter{nombre}captured by the route. In Joss, controller parameters must declare their type (string $nombre).var $nombreLimpio = trim($nombre): Trims any leading or trailing whitespace.return view("saludo", {"nombre": $nombreLimpio}):- Calls the native function
view(...). - The first argument is the name of the template file (it will look for
app/views/saludo.joss.html). - The second argument is an associative map with the data we want to inject into the HTML.
- Calls the native function
6. Generate and Design the HTML View with joss make:view
Generate the view file from your terminal by running:
joss make:view saludo
You will see the confirmation:
Created: app/views/saludo.joss.html
By default, joss make:view creates a template connected to the master layout (@extends('layouts.master')). For this first self-contained example, open app/views/saludo.joss.html and replace its content with this complete HTML card:
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Saludo - Mi Web Joss</title>
<style>
body { font-family: system-ui, sans-serif; margin: 40px; background: #f8fafc; color: #1e293b; }
.card { background: white; padding: 30px; border-radius: 12px; box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1); max-width: 500px; }
h1 { color: #0f766e; margin-top: 0; }
</style>
</head>
<body>
<div class="card">
<h1>¡Hola, {{ $nombre }}!</h1>
<p>Bienvenido a tu primera aplicación web construida con <strong>Joss</strong>.</p>
</div>
</body>
</html>
Native Security Against XSS Attacks:
Notice the {{ $nombre }} syntax:
- Joss’s template engine automatically escapes dangerous HTML characters such as <, >, ", or &.
- If a malicious user tries to enter <script>alert('hack')</script> as a name, Joss will convert it into safe entities (<script>...), protecting your visitors from Cross-Site Scripting (XSS) vulnerabilities.
7. Start and Test the Server
In your terminal, located at the root of the saludo_web project, type:
joss server start
(Or its equivalent shortcut joss server). You will see the HTTP server confirmation message:
[CLI] Ejecutando script de inicio (main.joss)...
[Joss Server] Servidor HTTP escuchando en http://127.0.0.1:8080
[Joss Server] Presiona 'q' o Ctrl+C para detener el servidor.
Open your web browser and visit:
http://127.0.0.1:8080/saludo/Ana
You will see the styled card greeting Ana. Now try changing the URL to:
http://127.0.0.1:8080/saludo/Carlos
The server will respond instantly with the new greeting.
To stop the server at any time, simply press the q key or Ctrl + C in your terminal.
8. Verification and Static Checking
Before deploying to production, you can audit the entire project with Joss’s verification commands:
joss analyze main.joss
joss check .
9. Practical Exercises
Level 1 — Quick Test
Pass a second piece of data from SaludoController@show to the view by adding "rol": "Desarrollador Joss" to the view(...) map and display it inside saludo.joss.html below the title using <p>Rol: {{ $rol }}</p>.
Level 2 — Guided Exercise
- Open
api.joss(orroutes.joss) and register a routeRouter::get("/api/estado", "SaludoController@estado"). - Add in
SaludoControllerthe methodpublic func estado()that returnsResponse::json({"ok": true, "servidor": "Joss HTTP"}). - Visit
http://127.0.0.1:8080/api/estadoin your browser and check the JSON response.
Level 3 — Challenge
- Register a route with two dynamic parameters:
Router::get("/calcular/{a}/{b}", "SaludoController@sumar"). - Implement
public func sumar(string $a, string $b)inSaludoController, convert both parameters withintval($a)andintval($b), calculate their sum, and render a view or JSON response with the operation details.
Next Step
Now that you know the complete lifecycle of a web request, you can explore the advanced capabilities of the Joss web stack and the complete type system of the language:
Continue with: Type system and coercion, HTTP and Controllers, Middlewares and Security, and GranDB Models.