Creare un server Express con TypeScript

LT
Luca Terribili
Autore

Nei tutorial precedenti ho installato Node.js e npm, preparato l'ambiente di sviluppo e configurato TypeScript. Ho anche verificato che il compilatore funzioni correttamente e che sia possibile eseguire file TypeScript utilizzando tsx. Adesso posso iniziare a costruire il backend vero e proprio.

L'obiettivo di questo articolo è creare un server HTTP utilizzando Express, il framework che ho scelto per gestire le richieste provenienti dai client. Un server di questo tipo può ricevere richieste da un frontend, elaborare i dati e restituire risposte in formato JSON. È la base su cui costruirò le API REST che, nei prossimi tutorial, potranno interagire con MongoDB.

Per ora manterrò il progetto semplice. Non aggiungerò ancora il database, l'autenticazione o la validazione avanzata dei dati: prima voglio capire come si avvia un server, come si definisce una rotta e come si restituisce una risposta HTTP. Aggiungere troppe componenti contemporaneamente rende più difficile capire dove si trova un eventuale problema.

Continuerò a utilizzare TypeScript e gli ES Modules configurati negli articoli precedenti. Durante lo sviluppo eseguirò il codice con tsx, mentre utilizzerò tsc per controllare i tipi e compilare il progetto. Questa distinzione è importante: eseguire un programma e verificarne la correttezza statica sono due operazioni differenti.

Verificare la struttura del progetto

Prima di iniziare, entro nella directory del progetto:

cd ~/dev/node-study

Controllo che siano presenti i file di configurazione:

ls -la

Nel progetto dovrebbero esserci almeno package.json e tsconfig.json, oltre alla directory node_modules, creata da npm quando ho installato le dipendenze.

La directory src contiene il codice sorgente TypeScript. La directory dist, invece, verrà utilizzata per conservare i file JavaScript generati dal compilatore. Questa separazione permette di mantenere distinti i file che modifico manualmente da quelli prodotti automaticamente.

Verifico anche che Express sia installato:

npm ls express typescript tsx

Se i pacchetti risultano presenti, non è necessario reinstallarli. Nel mio progetto Express, TypeScript e tsx sono già stati aggiunti durante la preparazione dell'ambiente di sviluppo.

Creare il primo server Express

Il primo passo consiste nel sostituire il programma di prova creato nel tutorial precedente con un vero server HTTP.

Apro src/index.ts con il mio editor e inserisco questo codice:

import express from 'express';

const app = express();
const port = 3000;

app.use(express.json());

app.get('/', (_req, res) => {
  res.json({
    message: 'Backend Node.js funzionante',
  });
});

app.listen(port, () => {
  console.log(`Server in ascolto sulla porta ${port}`);
});

La prima istruzione importa Express:

import express from 'express';

Sto utilizzando la sintassi degli ES Modules, già configurata nel progetto attraverso il campo type di package.json e le opzioni module e moduleResolution di tsconfig.json.

La riga successiva crea l'applicazione:

const app = express();

La variabile app rappresenta l'istanza di Express. Attraverso questo oggetto potrò registrare le rotte, aggiungere middleware e configurare il comportamento del server.

Definisco poi la porta:

const port = 3000;

La porta è il punto attraverso il quale il server accetta le connessioni TCP. In questo caso utilizzo la porta 3000, una scelta comune durante lo sviluppo locale. Non è una porta obbligatoria: potrò modificarla quando introdurrò una configurazione basata su variabili d'ambiente.

Comprendere il middleware JSON

Nel codice ho inserito questa istruzione:

app.use(express.json());

Express utilizza i middleware per elaborare le richieste prima che raggiungano le rotte. Un middleware può, per esempio, leggere il corpo di una richiesta, verificare un token di autenticazione o registrare informazioni utili per il debugging.

express.json() è un middleware integrato che analizza il corpo delle richieste contenenti JSON, rendendo i dati disponibili attraverso req.body.

Questo sarà utile quando creerò endpoint che accettano dati inviati da un frontend. Per esempio, una richiesta POST potrebbe contenere il nome e l'indirizzo email di un utente. Il middleware permette a Express di interpretare il contenuto JSON, ma non verifica automaticamente che i valori siano corretti o sicuri. La validazione dei dati verrà affrontata separatamente.

Definire la prima rotta HTTP

La parte centrale del nostro server è questa:

app.get('/', (_req, res) => {
  res.json({
    message: 'Backend Node.js funzionante',
  });
});

Con app.get() registro una rotta che risponde alle richieste HTTP di tipo GET dirette al percorso /, cioè alla radice del server.

Il primo argomento, '/', identifica il percorso. Il secondo è una funzione che Express esegue quando riceve una richiesta compatibile con quella rotta.

La funzione riceve normalmente due parametri principali: req, che rappresenta la richiesta, e res, che rappresenta la risposta. Nel nostro esempio il primo parametro è scritto _req perché non viene utilizzato. Il carattere iniziale non ha un significato speciale per JavaScript o TypeScript, ma è una convenzione utile per indicare che il parametro è intenzionalmente inutilizzato.

Il parametro res viene invece utilizzato per inviare la risposta:

res.json({
  message: 'Backend Node.js funzionante',
});

Il metodo json() serializza l'oggetto JavaScript in JSON e invia la risposta al client, impostando anche l'appropriato tipo di contenuto.

La risposta sarà simile a questa:

{
  "message": "Backend Node.js funzionante"
}

Express restituisce normalmente lo stato HTTP 200 OK quando la rotta termina correttamente e invia la risposta senza che sia stato impostato un codice diverso.

Avviare il server con tsx

Adesso posso eseguire il programma. Dal terminale, nella directory del progetto, lancio:

npx tsx src/index.ts

Qui utilizzo tsx, non tsc. Il primo permette di eseguire il file TypeScript durante lo sviluppo; il secondo è il compilatore che controlla il progetto e genera JavaScript.

Se tutto funziona, il terminale mostrerà:

Server in ascolto sulla porta 3000

Il processo rimarrà attivo perché Express sta aspettando nuove connessioni. È il comportamento previsto: un server non deve terminare subito dopo aver stampato un messaggio, altrimenti sarebbe un servizio decisamente poco utile.

Per fermarlo posso premere Ctrl+C nel terminale in cui è in esecuzione.

Verificare la risposta con curl

Per provare il server apro un secondo terminale, lasciando il primo in esecuzione. Utilizzo curl, uno strumento da riga di comando che permette di effettuare richieste HTTP.

Eseguo:

curl -i http://localhost:3000/

L'opzione -i mostra anche gli header HTTP della risposta, oltre al corpo. Se tutto è configurato correttamente, dovrei ottenere un risultato simile a questo:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"message":"Backend Node.js funzionante"}

Gli header possono variare in base alla versione di Express e agli altri middleware, ma il punto importante è che il server restituisca lo stato 200 OK e il contenuto JSON previsto.

L'indirizzo localhost indica il computer sul quale sto lavorando. La porta 3000 identifica il server e il percorso / corrisponde alla rotta definita con app.get().

Posso anche effettuare la richiesta senza mostrare gli header:

curl http://localhost:3000/

In questo caso vedrò soltanto il corpo della risposta.

Se il comando restituisce un errore di connessione, verifico innanzitutto che il server sia ancora attivo nel primo terminale e che non siano comparsi errori durante l'avvio. Se la porta è già occupata da un altro processo, dovrò individuare il processo responsabile oppure utilizzare una porta differente.

Controllare il codice con TypeScript

Il fatto che il server si avvii correttamente non significa che io abbia eseguito tutti i controlli disponibili. TypeScript può individuare incompatibilità tra i tipi anche prima di avviare l'applicazione.

Per controllare il progetto senza generare file JavaScript eseguo:

npx tsc --noEmit

Non specifico direttamente src/index.ts, perché voglio che il compilatore carichi le impostazioni di tsconfig.json. Con TypeScript 7, indicare esplicitamente i file sulla riga di comando può impedire il caricamento automatico della configurazione e produrre l'errore TS5112.

Se il comando termina senza errori, il controllo statico è andato a buon fine. Questo non garantisce che il programma sia privo di qualsiasi problema a runtime, ma consente di individuare molti errori prima dell'esecuzione.

È importante ricordare anche che TypeScript non controlla automaticamente la validità dei dati ricevuti dai client durante l'esecuzione. Quando aggiungerò endpoint che accettano dati esterni, dovrò introdurre regole di validazione specifiche.

Compilare il server in JavaScript

Finora ho utilizzato tsx per avviare il server. Per verificare anche la compilazione tradizionale, eseguo:

npx tsc

Il compilatore legge tsconfig.json, analizza il progetto e genera i file JavaScript nella directory dist, secondo le impostazioni configurate in precedenza.

Posso controllare il risultato con:

find dist -maxdepth 2 -type f

Dovrei trovare dist/index.js. A questo punto posso avviare il codice compilato direttamente con Node.js:

node dist/index.js

Anche in questo caso il server dovrebbe avviarsi sulla porta 3000 e rispondere alla richiesta effettuata con curl.

La differenza è che ora Node.js esegue il JavaScript generato dal compilatore, anziché utilizzare tsx per eseguire direttamente il sorgente TypeScript.

Durante lo sviluppo continuerò a usare tsx per praticità. La compilazione con tsc sarà invece utile per verificare il progetto e preparare i file da eseguire nell'ambiente di destinazione.

Conclusione

In questo tutorial ho creato il primo server Express del progetto, configurando una rotta HTTP GET che restituisce una risposta JSON. Ho visto come funzionano l'istanza di Express, il middleware JSON, la definizione delle rotte e il metodo res.json().

Ho inoltre verificato il server con curl, controllato il codice attraverso tsc --noEmit e provato a compilare il progetto per eseguire il JavaScript generato con Node.js.

Il backend è ancora essenziale, ma adesso possiede una struttura funzionante sulla quale potrò costruire le API REST. Nel prossimo tutorial organizzerò meglio il codice, separando il punto di ingresso del server dalle rotte e preparando una struttura più adatta a un'applicazione che crescerà con l'introduzione di MongoDB, della validazione e della gestione degli errori.