PR507409 — Aplicació web amb gràfics de Metabase incrustats sobre SQL Server
Tipus: Pràctica tècnica individual (JavaScript + Metabase + SQL Server) Durada estimada: 6 hores Lliurament: Campus Virtual — repositori de codi (backend + frontend) + captures de pantalla + document de reflexió
Objectius
Al finalitzar aquesta pràctica, l'alumne serà capaç de:
- Desplegar una instància de SQL Server amb Docker i carregar-hi un conjunt de dades relacional propi.
- Connectar Metabase a SQL Server i crear preguntes i un dashboard que responguin a objectius de negoci concrets.
- Activar i configurar el mode d'incrustació signada (Signed Embedding) de Metabase amb una clau secreta.
- Construir una aplicació web amb Node.js/Express i JavaScript que generi tokens JWT signats en un backend i incrusti els gràfics de Metabase com a
<iframe>en una pàgina pròpia, sense exposar mai la clau secreta al navegador. - Explicar per què la incrustació signada és necessària quan un dashboard conté dades sensibles o ha de restringir-se per usuari (row-level permissions), a diferència de la incrustació pública.
Materials necessaris
- Docker Desktop instal·lat i en execució (versió 24 o superior).
- Node.js 20 o superior i npm.
- Un client SQL (Azure Data Studio, DBeaver o
sqlcmd) per verificar les dades carregades a SQL Server. - Els continguts de Metabase i Power BI (per al concepte de model de dades i dashboard) d'aquest bloc.
- Documentació oficial: Metabase — Signed embedding, Metabase — Static embed parameters, SQL Server on Linux Docker images.
Diferència amb la pràctica PR507407
A PR507407 vas construir un dashboard dins de Power BI. En aquesta pràctica el dashboard viu a Metabase, però l'usuari final el veu incrustat dins d'una aplicació web pròpia que tu construeixes, sense necessitat d'obrir Metabase directament. És el patró que fan servir productes SaaS reals per oferir analítica als seus clients ("embedded analytics").
Descripció de la pràctica
L'objectiu final és una petita aplicació web (una pàgina HTML servida per un backend Express) que mostra un o més gràfics de Metabase incrustats, on les dades provenen d'una base de dades SQL Server que tu mateix aixeques i alimentes.
flowchart LR
A["SQL Server\nDocker"] --> B["Metabase\nconnexió + dashboard"]
B -- "Signed Embedding\nJWT" --> C["Backend Express\nsigna el token"]
C --> D["Frontend\niframe amb el token"]
D --> E["Usuari final\nnavegador"]
El flux de seguretat és el nucli tècnic de la pràctica: la clau secreta d'incrustació mai viatja al navegador. El backend és qui la coneix, signa un JWT amb l'identificador del dashboard (i, opcionalment, paràmetres de filtratge), i entrega al frontend només la URL final de l'iframe amb el token ja signat.
Part 1 — SQL Server amb Docker (1 hora)
Aixeca un contenidor de SQL Server 2022 amb Docker Compose:
# docker-compose.yml
services:
sqlserver-nom-cognom:
image: mcr.microsoft.com/mssql/server:2022-latest
container_name: sqlserver-nom-cognom
environment:
ACCEPT_EULA: "Y"
MSSQL_SA_PASSWORD: "Bigdata2026!"
MSSQL_PID: "Developer"
ports:
- "1433:1433"
volumes:
- sqlserver_data_nom_cognom:/var/opt/mssql
healthcheck:
test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P \"$$MSSQL_SA_PASSWORD\" -C -Q 'SELECT 1' || exit 1"]
interval: 10s
retries: 10
volumes:
sqlserver_data_nom_cognom:
Connecta't amb el teu client SQL preferit (host localhost, port 1433, usuari sa, contrasenya de l'entorn) i crea una base de dades bigdata_nom_cognom amb almenys dues taules relacionades (per exemple clients i comandes, o productes i vendes) amb un mínim de 500 files en total. Pots reutilitzar i adaptar l'esquema i el generador de dades de la pràctica PR507402, traduint la sintaxi de PostgreSQL a Transact-SQL on calgui (per exemple, IDENTITY(1,1) en lloc de SERIAL).
SA_PASSWORD ha de complir la política de complexitat
SQL Server rebutja contrasenyes febles per a l'usuari sa: com a mínim 8 caràcters, amb majúscules, minúscules, dígits i un símbol. Si el contenidor es reinicia constantment, revisa docker compose logs sqlserver-nom-cognom — sovint l'error és aquest.
Part 2 — Metabase connectat a SQL Server (1 hora)
Amplia el mateix docker-compose.yml amb el servei de Metabase (com a Metabase), assegurant-te que comparteix xarxa amb sqlserver-nom-cognom:
metabase-nom-cognom:
image: metabase/metabase:v0.50
container_name: metabase-nom-cognom
ports:
- "3001:3000"
volumes:
- metabase_data_nom_cognom:/metabase-data
environment:
MB_DB_FILE: /metabase-data/metabase.db
depends_on:
sqlserver-nom-cognom:
condition: service_healthy
A Configuració d'administrador → Bases de dades → Afegeix una base de dades, tria SQL Server i introdueix Host sqlserver-nom-cognom, port 1433, usuari sa i la contrasenya configurada.
Crea, com a mínim, dues preguntes (Question builder o SQL natiu) que responguin a objectius de negoci concrets (per exemple, evolució temporal de vendes i rànquing per categoria) i afegeix-les a un Dashboard nou.
Part 3 — Activar la incrustació signada (Signed Embedding) (1 hora)
A Configuració d'administrador → Configuració → Incrustació (Embedding):
- Activa la incrustació (si Metabase la té desactivada per defecte, activa primer "Allow embedding in other applications").
- Genera i copia la clau secreta d'incrustació (Embedding secret key): una cadena llarga que no ha d'aparèixer mai al codi del frontend ni en cap fitxer pujat a un repositori públic.
- Al teu dashboard, obre Compartir → Incrustar aquest dashboard (Embed this dashboard) i activa-hi la incrustació signada. Tria quins filtres del dashboard vols que siguin blocats (fixats pel backend, no manipulables des del navegador) i quins editables per l'usuari.
- Anota l'identificador numèric del dashboard (visible a la URL de Metabase, per exemple
.../dashboard/3-...).
La clau secreta és per al backend, mai per al frontend
Si la clau secreta d'incrustació s'inclou en JavaScript que s'executa al navegador (per exemple, en un fitxer .js servit estàticament), qualsevol visitant podria llegir-la i generar tokens propis amb accés il·limitat a totes les dades del dashboard, saltant-se qualsevol restricció de fila o de filtre. Ha de viure únicament en una variable d'entorn del servidor.
Part 4 — Backend Express: signatura del token JWT (1,5 hores)
Crea un projecte Node.js senzill:
mkdir pr507409-nom_cognom && cd pr507409-nom_cognom
npm init -y
npm install express jsonwebtoken dotenv
// server.js
require('dotenv').config();
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
const METABASE_SITE_URL = process.env.METABASE_SITE_URL; // p. ex. http://localhost:3001
const METABASE_SECRET_KEY = process.env.METABASE_SECRET_KEY;
const DASHBOARD_ID = Number(process.env.METABASE_DASHBOARD_ID);
app.use(express.static('public'));
app.get('/api/embed-url', (req, res) => {
const payload = {
resource: { dashboard: DASHBOARD_ID },
params: {}, // paràmetres blocats, p. ex. { client_id: req.session.clientId }
exp: Math.round(Date.now() / 1000) + 10 * 60 // el token caduca als 10 minuts
};
const token = jwt.sign(payload, METABASE_SECRET_KEY);
const iframeUrl = `${METABASE_SITE_URL}/embed/dashboard/${token}#bordered=true&titled=true`;
res.json({ iframeUrl });
});
app.listen(3000, () => console.log('App escoltant a http://localhost:3000'));
# .env (mai el pugis a un repositori públic)
METABASE_SITE_URL=http://localhost:3001
METABASE_SECRET_KEY=la-clau-copiada-de-metabase
METABASE_DASHBOARD_ID=1
Per què el token caduca
El camp exp limita la validesa del token a una finestra curta. Si l'iframe es manté obert més estona que la finestra de caducitat, cal que el frontend torni a demanar un token nou a /api/embed-url — exactament com passa amb qualsevol sessió d'autenticació basada en JWT.
Part 5 — Frontend: incrustar el gràfic (1 hora)
Crea public/index.html, que demana la URL signada al backend i la injecta en un <iframe>:
<!DOCTYPE html>
<html lang="ca">
<head>
<meta charset="UTF-8">
<title>Dashboard — nom_cognom</title>
<style>
iframe { width: 100%; height: 800px; border: none; }
</style>
</head>
<body>
<h1>El meu dashboard de vendes</h1>
<iframe id="metabase-frame"></iframe>
<script>
fetch('/api/embed-url')
.then(res => res.json())
.then(data => {
document.getElementById('metabase-frame').src = data.iframeUrl;
})
.catch(err => console.error('Error carregant el dashboard:', err));
</script>
</body>
</html>
Arrenca el backend (node server.js) i obre http://localhost:3000: el dashboard de Metabase hauria d'aparèixer incrustat dins la teva pròpia pàgina, sense cap barra de navegació ni menú de Metabase.
Part 6 — Filtratge dinàmic (opcional, +0,5 hores d'aprofundiment)
Amplia /api/embed-url perquè el payload.params inclogui un valor blocat derivat d'una petita simulació d'autenticació (per exemple, un ?client=... a la URL que el backend valida abans de generar el token). Comprova que, si canvies el valor blocat, el dashboard mostra només les dades corresponents — demostrant com un mateix dashboard es pot personalitzar per usuari sense que el navegador pugui alterar el filtre.
Lliurament
Puja al Campus Virtual:
| Fitxer | Contingut | Format |
|---|---|---|
pr507409_cognom_nom.zip |
Codi complet: docker-compose.yml, script/SQL de creació de dades, server.js, public/index.html, .env.example (sense la clau real) |
.zip |
captures_cognom_nom.pdf |
Captures del dashboard a Metabase i de la mateixa aplicació incrustant-lo | |
reflexio_cognom_nom.pdf |
Respostes a les preguntes de reflexió final |
Requisits mínims per ser avaluat
- SQL Server ha de contenir com a mínim dues taules relacionades amb dades reals o realistes.
- Metabase ha d'estar connectat a SQL Server (no a un altre motor) i el dashboard ha de tenir com a mínim dues visualitzacions.
- La incrustació ha de ser signada (JWT), no pública ni estàtica sense token.
- La clau secreta no pot aparèixer en cap fitxer del frontend ni en cap commit del repositori entregat (revisa
.gitignorei.env.example). - L'aplicació ha de funcionar en local seguint només les instruccions del
README.
Consulta la Rúbrica PR507409 per als criteris detallats d'avaluació.
Preguntes de reflexió final
Un cop completada la pràctica, respon per escrit:
- Quina diferència hi ha entre la incrustació pública (public embedding) i la incrustació signada (signed embedding) de Metabase? Quan triaries cadascuna?
- Per què la clau secreta d'incrustació ha d'estar només al backend? Què podria fer un atacant si l'obtingués?
- Quin paper juga el camp
expdel JWT? Què passaria si no el poséssim mai? - Com adaptaries
server.jsperquè cada usuari autenticat vegi només les seves pròpies dades (per exemple, només les vendes de la seva regió), sense canviar res al dashboard de Metabase? - Compara aquesta arquitectura d'"embedded analytics" amb el dashboard de Power BI de la pràctica PR507407: quins avantatges i inconvenients té incrustar Metabase dins una aplicació pròpia enfront de compartir directament un fitxer o enllaç de Power BI?
Pràctica PR507409 | Mòdul M5074 Sistemes de Big Data | Institut Sa Palomera (Blanes) | Curs CEIABD 2026-2027