Van frontend naar database in kleine, begrijpelijke stappen
Pad Architectuur → Frontend → Backend → Database → Veiligheid
Frontend en backend communiceren via HTTP. Server beheert de database.
mkdir chat-app && cd chat-app
npm init -y
npm install sqlite3
mkdir public
server.mjs → backend code (.mjs = ES modules)public/index.html → frontend
<button id="laadBerichten">Laad berichten</button>
<form id="verstuurContainer">
<label for="verstuur_input">Bericht:</label>
<input id="verstuur_input" type="text">
<button type="submit">Verstuur</button>
</form>
<div id="berichtenContainer"></div>
const toonBerichten = (data) => {
const container = document.getElementById('berichtenContainer')
container.innerHTML = ''
data.forEach(bericht => {
const div = document.createElement('div')
div.classList.add('bericht')
const naam = document.createElement('p')
naam.textContent = bericht.name
const tekst = document.createElement('p')
tekst.textContent = bericht.message
div.appendChild(naam)
div.appendChild(tekst)
container.appendChild(div)
})
}
✓ Gebruikt textContent voor veiligheid (geen XSS)
const fetchBerichten = () => {
fetch('/messages')
.then(res => res.json())
.then(data => toonBerichten(data))
}
// Koppel aan knop
document.getElementById('laadBerichten')
.addEventListener('click', fetchBerichten)
document.getElementById('verstuurContainer')
.addEventListener('submit', (e) => {
e.preventDefault()
const input = document.getElementById('verstuur_input')
const tekst = input.value.trim()
if (!tekst) return
fetch('/messages', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: tekst,
user_id: 1
})
})
.then(() => {
input.value = ''
fetchBerichten()
})
})
.bericht {
display: flex;
gap: 1rem;
padding: 0.5rem;
border-bottom: 1px solid #eee;
}
.bericht p:first-child {
font-weight: bold;
min-width: 100px;
}
import sqlite3 from 'sqlite3'
import fs from 'fs'
import path from 'path'
import http from 'http'
const __dirname = import.meta.dirname // Node 22+
const db = new sqlite3.Database('chat.db', (err) => {
if (err) {
console.error('Database error:', err)
return
}
console.log('Database connected')
createTables()
})
path.dirname(fileURLToPath(import.meta.url))
const createTables = () => {
db.exec(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER
);
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
message TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
`, (err) => {
if (err) console.error(err)
else insertUser('Anton', 25) // Demo user
})
}
const insertUser = (name, age) => {
const stmt = db.prepare('INSERT INTO users (name, age) VALUES (?, ?)')
stmt.run(name, age)
stmt.finalize()
}
const insertMessage = (user_id, message) => {
const stmt = db.prepare(
'INSERT INTO messages (user_id, message) VALUES (?, ?)'
)
stmt.run(user_id, message, (err) => {
if (err) console.error('Insert error:', err)
})
stmt.finalize()
}
const getAllMessages = (res) => {
const query = `
SELECT
messages.id as msg_id,
users.name,
messages.message,
messages.created_at
FROM messages
INNER JOIN users ON messages.user_id = users.id
ORDER BY messages.created_at DESC
`
db.all(query, (err, rows) => {
if (err) {
res.statusCode = 500
return res.end(JSON.stringify({ error: 'Database error' }))
}
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify(rows))
})
}
const server = http.createServer((req, res) => {
// Serveer HTML
if (req.method === 'GET' && req.url === '/') {
const htmlPath = path.join(__dirname, 'public', 'index.html')
fs.readFile(htmlPath, 'utf8', (err, data) => {
if (err) {
res.statusCode = 500
return res.end('Error loading page')
}
res.setHeader('Content-Type', 'text/html')
res.end(data)
})
}
// ... routes volgen
})
// GET messages
else if (req.method === 'GET' && req.url === '/messages') {
getAllMessages(res)
}
// POST message
else if (req.method === 'POST' && req.url === '/messages') {
let body = ''
req.on('data', chunk => body += chunk)
req.on('end', () => {
try {
const { user_id, message } = JSON.parse(body)
if (!message?.trim()) {
res.statusCode = 400
return res.end(JSON.stringify({ error: 'Message required' }))
}
insertMessage(user_id, message)
res.statusCode = 201
res.end(JSON.stringify({ success: true }))
} catch (e) {
res.statusCode = 400
res.end(JSON.stringify({ error: 'Invalid JSON' }))
}
})
}
// 404 handler
else {
res.statusCode = 404
res.end('Not found')
}
})
const PORT = 3000
server.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`)
})