Handleiding: Moderne Multi-Service Full-Stack Web App

Met Nginx, een frontend website en backends met Node.js en Rust.

Inhoud:

Deze handleiding helpt je een webserver op te zetten die het volgende host:

  1. Een frontend website (HTML, CSS, PHP, JS)
  2. Een Node.js backend (service)
  3. Een Rust backend (service)
  4. Nginx als reverse proxy en frontend-server

Whats new?

De browser communiceert enkel met Nginx en die bepaalt wat er gebeurt:

  • Frontend-verzoeken → HTML/CSS/PHP/JS bestanden
  • API-verzoeken → doorgestuurd naar Node.js of Rust backend

Stap 1: Frontend Website

In dit voorbeeld staan de websitebestanden in:

/var/www/testlab.computerbas.nl/public/

Bestanden:

index.html
styles.css
app.js

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <title>My first Multi-Service Full-Stack Web App</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <h1>Multi-Service Full-Stack App</h1>
  <button id="nodeBtn">Get Data from Node.js</button>
  <button id="rustBtn">Get Data from Rust</button>
  <pre id="results"></pre>
  <script src="app.js"></script>
</body>
</html>

app.js:

document.addEventListener('DOMContentLoaded', () => {
  const nodeBtn = document.getElementById('nodeBtn');
  const rustBtn = document.getElementById('rustBtn');
  const resultsDiv = document.getElementById('results');

  nodeBtn.addEventListener('click', async () => {
    const response = await fetch('/api/nodejs/data');
    const data = await response.json();
    resultsDiv.textContent = JSON.stringify(data, null, 2);
  });

  rustBtn.addEventListener('click', async () => {
    const response = await fetch('/api/rust/data');
    const data = await response.json();
    resultsDiv.textContent = JSON.stringify(data, null, 2);
  });
});

Stap 2: Backend services

Deze backend-apps draaien lokaal (127.0.0.1) op verschillende poorten en zijn niet direct publiek toegankelijk.

A) Node.js API

  1. Maak een directory, bijvoorbeeld /home/bas/nodejs/
  2. Voer uit: npm init -y en npm install express
  3. Maak een bestand server.js

server.js:

const express = require('express');
const app = express();
const PORT = 3002;

app.get('/data', (req, res) => {
  res.json({ service: 'Node.js', message: 'Hello from the Node API!' });
});

app.listen(PORT, '127.0.0.1', () => {
  console.log(`Node.js API server listening on http://127.0.0.1:${PORT}`);
});

B) Rust API

  1. Maak een nieuw project: cargo new rust-app
  2. Voeg Axum en Tokio toe: cargo add axum tokio -F tokio/full

in src/main.rs:

use axum::{routing::get, Json, Router};
use serde_json::{json, Value};

async fn get_data_handler() -> Json<Value> {
    Json(json!({
        "service": "Rust",
        "message": "Hello from the Rust API!"
    }))
}

#[tokio::main]
async fn main() {
    let app = Router::new().route("/data", get(get_data_handler));
    let listener = tokio::net::TcpListener::bind("127.0.0.1:3003")
        .await
        .unwrap();

    println!("Rust API listening on {}", listener.local_addr().unwrap());
    axum::serve(listener, app).await.unwrap();
}

Stap 3: Services beheren met systemd

Maak systemd-services aan zodat de backends automatisch starten en herstarten.

nodejs.service

[Unit]
Description=Node.js API Service
After=network.target

[Service]
User=bas
WorkingDirectory=/home/bas/nodejs/
ExecStart=/home/bas/nodejs/server.js
Restart=always

[Install]
WantedBy=multi-user.target

rust.service

[Unit]
Description=Rust API Service
After=network.target

[Service]
User=bas
WorkingDirectory=/home/bas/rust
ExecStart=/home/bas/rust/rust-app
Restart=always

[Install]
WantedBy=multi-user.target

Stap 4: Nginx configureren als reverse proxy

Maak een configuratiebestand aan:

sudo nano /etc/nginx/sites-available/testlab.conf

in testlab.conf:

server {
  listen 80;
  server_name testlab.computerbas.nl;
  return 301 https://$host$request_uri;
}

server {
  listen 443 ssl http2;
  server_name testlab.computerbas.nl;

  root /var/www/testlab.computerbas.nl/public;
  index index.html index.htm;

  location / {
    try_files $uri $uri/ =404;
  }

  location /api/nodejs/ {
    proxy_pass http://127.0.0.1:3002/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }

  location /api/rust/ {
    proxy_pass http://127.0.0.1:3003/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}

Resultaat

Een volledig functionerende, multi-service webapplicatie.

  • Frontend met Nginx
  • Node.js en Rust backends communiceren via API-routes
  • Beveiligd, schaalbaar en eenvoudiger te onderhouden