Come distribuire un’applicazione Deno?

How to Deploy a Deno Application_
How to Deploy a Deno Application_

Esistono diverse opzioni di distribuzione per le applicazioni web realizzate con Deno. Tuttavia, la piattaforma di containerizzazione come servizio è diventata una scelta popolare negli ultimi tempi, grazie ai vari vantaggi che offre rispetto alle altre opzioni di distribuzione.

In questo articolo esplorerete Deno, i suoi vantaggi e i suoi limiti. Inoltre, costruirete una semplice applicazione Deno e la distribuirete su container Back4app.

Che cos’è Deno?

Deno è un runtime sicuro e moderno per JavaScript e TypeScript, creato per risolvere le limitazioni e i difetti di progettazione di Node.js.

A differenza di Node.js, enfatizza la sicurezza per impostazione predefinita, imponendo permessi granulari per l’accesso al file system e alla rete.

Inoltre, Deno supporta in modo nativo TypeScript, eliminando la necessità di ulteriori passaggi di configurazione o trasposizione, tra le altre caratteristiche.

Dal suo rilascio nel 2018, Deno ha raccolto l’attenzione e l’interesse degli sviluppatori grazie ai suoi miglioramenti rispetto a Node.js.

Tuttavia, mentre Deno offre dei miglioramenti, Node.js rimane un ecosistema maturo con un ampio supporto della comunità e un vasto archivio di pacchetti.

Ciononostante, Deno ha attirato una comunità crescente di sviluppatori che ne apprezzano l’approccio e ne esplorano il potenziale.

Vantaggi di Deno

La popolarità di Deno è dovuta ad alcune ragioni di fondo. Alcune sono le seguenti.

Sicurezza migliorata rispetto a Node.js

Deno offre come vantaggio principale una maggiore sicurezza, implementando un modello di sicurezza basato sui permessi ed eseguendo le applicazioni in un ambiente sandbox.

Implementa un modello di sicurezza basato sui permessi, in cui è richiesta un’autorizzazione esplicita per accedere a risorse come il file system e la rete.

Per impostazione predefinita, Deno opera in modalità ristretta ed esegue le applicazioni in un ambiente sandbox, limitando le azioni potenzialmente rischiose, isolandole dal sistema sottostante e impedendo l’accesso diretto alle risorse sensibili.

Controlli di sicurezza completi e revisioni meticolose del codice rafforzano ulteriormente la solida sicurezza di Deno. Queste misure forniscono una piattaforma affidabile e sicura per la creazione di applicazioni, infondendo fiducia nella loro sicurezza e proteggendo da potenziali vulnerabilità.

Gestione delle dipendenze

Deno offre un approccio distinto alla gestione delle dipendenze rispetto ai tradizionali ambienti di runtime JavaScript come Node.js.

Invece di affidarsi a un registro centralizzato dei pacchetti, Deno utilizza gli URL per importare i moduli direttamente dal web.

Questo approccio semplifica il processo, eliminando la necessità di un gestore di pacchetti separato come npm e attenuando i problemi legati ai conflitti di versione e alla complessità della gestione della cartella “node_modules”.

È possibile specificare le dipendenze indicando gli URL dei moduli che si desidera importare, semplificando la condivisione e la distribuzione del codice. Questo approccio decentralizzato alla gestione delle dipendenze in Deno promuove la semplicità, riduce gli attriti e contribuisce a garantire un’esperienza di sviluppo più snella.

Supporto di TypeScript fuori dalla scatola

Deno offre un supporto nativo e continuo per TypeScript, il che lo rende una scelta eccellente se preferite o richiedete TypeScript nei vostri progetti.

TypeScript è un superset tipizzato di JavaScript che porta la tipizzazione statica e altre caratteristiche avanzate del linguaggio nello sviluppo di JavaScript. Con Deno, non c’è bisogno di ulteriori configurazioni o fasi di compilazione per utilizzare TypeScript.

Deno viene fornito con il compilatore TypeScript, che consente di scrivere ed eseguire direttamente codice TypeScript.

Questo supporto nativo elimina la complessità di impostare una toolchain TypeScript separata e semplifica il processo di sviluppo.

Consente di sfruttare il controllo dei tipi di TypeScript, gli strumenti migliorati e l’esperienza migliorata degli sviluppatori durante la creazione di applicazioni con Deno.

Limitazioni di Deno

Tuttavia, Deno presenta alcune limitazioni che ne condizionano l’adozione. Alcuni di essi sono i seguenti.

Ecosistema immaturo

Uno dei limiti di Deno è la maturità del suo ecosistema. Rispetto a Node.js, che esiste da più tempo, l’ecosistema di Deno è ancora relativamente nuovo e in evoluzione.

Ciò significa che le librerie, i framework e gli strumenti di terze parti progettati specificamente per Deno sono meno numerosi. Potrebbe essere necessario creare alcune funzionalità da zero o adattare pacchetti Node.js esistenti per l’uso in Deno.

Le dimensioni ridotte della comunità significano anche che potrebbero essere disponibili meno risorse, tutorial e supporto della comunità rispetto al consolidato ecosistema Node.js.

Tuttavia, con l’aumento della popolarità e dell’adozione di Deno, si prevede che il suo ecosistema cresca e maturi, offrendo in futuro una gamma più ampia di librerie e strumenti.

Curva di apprendimento ripida

Un altro limite di Deno è la curva di apprendimento associata alla transizione da altri ambienti di runtime JavaScript, come Node.js.

Deno introduce nuovi concetti, API e modelli con cui è necessario familiarizzare. Ciò include la comprensione del sistema di moduli di Deno, del modello di sicurezza basato sui permessi e delle differenze nel modo in cui alcune funzionalità sono implementate rispetto ad altri runtime.

Gli sviluppatori che sono già esperti di Node.js potrebbero dover investire tempo e fatica per imparare e adattarsi alle caratteristiche e alle convenzioni specifiche di Deno.

Tuttavia, la curva di apprendimento può essere gestita facendo riferimento alla documentazione ufficiale di Deno, impegnandosi con la comunità di Deno ed esplorando le risorse di apprendimento disponibili.

Compatibilità con le librerie Node.js

La compatibilità con le librerie Node.js è un’altra limitazione di Deno. A causa delle differenze nei sistemi di moduli e negli ambienti di esecuzione, non tutte le librerie e i moduli Node.js possono essere utilizzati direttamente in Deno senza modifiche.

Deno utilizza i moduli ES (moduli ECMAScript) come sistema di moduli, mentre Node.js utilizza tradizionalmente i moduli CommonJS. Questa differenza nei formati dei moduli può portare a incompatibilità quando si importano e si utilizzano moduli specifici di Node.js in Deno.

Gli sviluppatori potrebbero dover apportare modifiche o trovare librerie alternative progettate appositamente per funzionare con il sistema di moduli di Deno.

Sebbene Deno fornisca un livello di compatibilità per l’esecuzione di alcuni moduli Node.js, potrebbe non coprire tutti i casi e potrebbero essere necessarie modifiche o adattamenti manuali.

Opzioni di distribuzione di Deno

Esistono diverse opzioni di distribuzione per le applicazioni Deno, alcune delle quali includono le seguenti.

Infrastruttura come servizio (IaaS)

L’Infrastructure-as-a-Service (IaaS) è un modello di cloud computing che offre risorse informatiche virtualizzate. Con IaaS, è possibile affittare macchine virtuali, storage e reti dai fornitori di cloud su base pay-as-you-go. In questo modo è possibile configurare e gestire la propria infrastruttura virtualizzata senza investire in hardware fisico.

Le opzioni IaaS consentono di eseguire le applicazioni Deno su macchine virtuali. Le piattaforme IaaS più diffuse, come AWS, Google Cloud e Microsoft Azure, offrono soluzioni flessibili e scalabili, consentendo di configurare l’infrastruttura in base alle esigenze specifiche dell’applicazione.

Tuttavia, se da un lato l’IaaS garantisce un maggiore controllo e isolamento delle risorse, dall’altro richiede un’impostazione e una gestione più manuale, con attività quali il provisioning dei server, gli aggiornamenti della sicurezza e il monitoraggio.

Pertanto, l’IaaS è una scelta valida quando si richiede un ampio controllo sull’infrastruttura e si dispone delle competenze necessarie per gestirne efficacemente le complessità.

Container-as-a-Service (CaaS)

Container-as-a-Service (CaaS) è un modello di cloud computing che semplifica la distribuzione e la gestione di applicazioni containerizzate.

Con CaaS, potete concentrarvi sulla creazione e sulla distribuzione delle applicazioni senza preoccuparvi dell’infrastruttura sottostante.

Le applicazioni Deno possono essere distribuite all’interno di container, garantendo coerenza e isolamento. Back4app containers è un’opzione CaaS molto diffusa per la distribuzione di Deno.

Le piattaforme CaaS offrono scalabilità e isolamento delle risorse, con ogni applicazione in esecuzione nel proprio container, migliorando la sicurezza e la stabilità.

La coerenza dei container garantisce che le applicazioni Deno possano essere facilmente distribuite su qualsiasi piattaforma che supporti i container.

Pur comportando una curva di apprendimento, le soluzioni CaaS offrono vantaggi significativi per le applicazioni che richiedono la scalabilità dinamica e la distribuzione su più nodi o cluster.

Processo di installazione di Deno

Prima di poter utilizzare Deno, è necessario scaricarlo e installarlo. L’installazione di Deno varia a seconda del sistema operativo in uso.

Su macOS e Linux, è possibile installare Deno eseguendo il comando seguente:

curl -fsSL <https://deno.land/x/install/install.sh> | sh

In Windows, è possibile installare Deno utilizzando Powershell, eseguendo il comando seguente:

irm <https://deno.land/install.ps1> | iex

Per confermare che l’installazione è andata a buon fine, si può eseguire il comando seguente, che dovrebbe stampare un numero di versione sul terminale.

deno --version

Se non viene visualizzato il numero di versione, provare a installare nuovamente Deno.

Impostazione di un progetto Deno

Per creare una semplice API con Deno, sono necessari un router, un server e un database.

Prima di seguire i passi seguenti, creare una cartella src nella directory principale del progetto. Questa cartella conterrà tutti i file sorgente del progetto.

Passo 1: Creare un file di dipendenza

A differenza di Node.js, Deno non utilizza gestori di pacchetti come NPM o Yarn. Piuttosto, i pacchetti vengono importati direttamente dal loro URL.

Per imitare le funzioni di un file package.json, creare un file deps.ts nella cartella principale del progetto e aggiungervi il blocco di codice sottostante.

export { Application, Router } from "https://deno.land/x/[email protected]/mod.ts";
export type { RouterContext} from "https://deno.land/x/[email protected]/mod.ts";
export { config as dotenvConfig } from "https://deno.land/x/[email protected]/mod.ts";
export { Client } from "https://deno.land/x/[email protected]/mod.ts";

Il blocco di codice qui sopra importa (installa) ed esporta Application, Router e RouterContex da Oak. config da dotenv e Client da deno-postgres.

Passo 2: creazione di un server

In questo passo, si creerà un semplice server HTTP con Oak. Oak è un middleware per il server HTTP di Deno basato su Koa.js, un framework per Node.js simile a Express ma più leggero.

Per creare un server HTTP con Oak, creare un file server.ts in src e aggiungervi il blocco di codice sottostante.

import { Application } from "../deps.ts";
import config from "../config/default.ts";

import router from "./router.ts";

const app = new Application();

app.use(router.routes());
app.use(router.allowedMethods());

await app.listen({ port: config.port });

Il blocco di codice precedente crea un server HTTP con Oak e registra un router per gestire tutto il traffico in entrata.

La riga app.use(router.routes()) registra le rotte del router come middleware nell’applicazione Oak. Tutte le richieste in arrivo saranno confrontate con le rotte registrate e i gestori corrispondenti saranno eseguiti se viene trovata una corrispondenza.

Se non viene trovata una corrispondenza, la linea app.use(router.allowedMethods()) li gestisce inviando risposte appropriate, come un 404 not found o un 405 not allowed.

Fase 3: Gestione delle variabili ambientali

La memorizzazione di dati sensibili, come chiavi API, credenziali di database e così via, in testo normale rappresenta un rischio per la sicurezza. Chiunque entri in possesso delle chiavi o delle credenziali può avere accesso illimitato alla vostra applicazione. Ciò può comportare la perdita di dati e il furto di dati, oltre ad altri possibili exploit.

È considerata una buona pratica memorizzare i dati sensibili nelle variabili ambientali per evitare situazioni come questa.

Creare un file .env nella cartella principale del progetto e memorizzare le credenziali del database e altre informazioni sensibili nel file.

Così:

#.env
DB_URI = <YOUR_POSTGRES_DB_URI>
PORT = 8000

Sostituire con le credenziali del database.

Quindi, creare una cartella config nella cartella principale del progetto; nella cartella config, creare un file default.ts e aggiungervi il blocco di codice sottostante.

//default.ts
import { dotenvConfig } from "../deps.ts";

dotenvConfig({
  export: true,
  path: "../.env",
});

const config = {
  db: {
    dbUri: Deno.env.get("DB_URI"),
  },
  port: 3000
};

export default config;

Il blocco di codice precedente recupera in modo sicuro i valori memorizzati nel file .env e li espone al resto dell’applicazione.

Passo 3: connessione a un database

In questa fase, si collegherà l’applicazione a un database Postgres. Il database è necessario per memorizzare e recuperare i dati dell’applicazione.

Creare un file db.ts nella cartella src e aggiungervi il blocco di codice sottostante.

//db.ts
import { Client } from "../deps.ts";
import config from "../config/default.ts";

let postgresConfiguration = config.db.dbUri;

const client = new Client(postgresConfiguration);

await client.connect();

export default client;

Il blocco di codice precedente tenta di connettere l’applicazione a un database Postgres, utilizzando l’URI fornito nel file .env.

Passo 4: Creazione di un repository di database

Creare un file blogRepository nella cartella src e aggiungere il codice sottostante al file.

//blogRepository.ts
import client from "./db.ts";

class BlogRepository {
  async createBlogTable() {
    const blog = await client.queryArray(
      `CREATE TABLE IF NOT EXISTS blogs (id SERIAL PRIMARY KEY, title VARCHAR(255), body VARCHAR(255), author VARCHAR(255))`
    );

    return blog;
  }

  async getAllBlogs() {
    const allBlogs = await client.queryArray("SELECT * FROM blogs");

    return allBlogs;
  }

  async getBlogById(id: string) {
    const blog = await client.queryArray(
      `SELECT * FROM blogs WHERE id = ${id}`
    );

    return blog;
  }

  async createBlog(title: string, body: string, author: string) {
    const blog = await client.queryArray(
      `INSERT INTO blogs (title, body, author) VALUES ('${title}', '${body}', '${author}')`
    );

    return blog;
  }

  async updateBlog(id: string, title: string, body: string, author: string) {
    const blog = await client.queryArray(
      `UPDATE blogs SET title = '${title}', body = '${body}', author = '${author}' WHERE id = ${id}`
    );

    return blog;
  }

  async deleteBlog(id: string) {
    const blog = await client.queryArray(`DELETE FROM blogs WHERE id = ${id}`);

    return blog;
  }
}

export default new BlogRepository();

Il blocco di codice qui sopra gestirà tutte le operazioni sul database, astraendo le query SQL grezze ed esponendo semplici metodi che si possono usare per interagire con il database Postgres.

Passo 5: creazione di gestori di rotte

In questa fase, si creeranno gestori di rotte per gestire semplici funzioni CRUD per l’applicazione. Le rotte supportate sono le seguenti:

  • GET /api/blogs: Restituisce tutti i blog presenti nel database
  • GET /api/blog/:id: Restituisce un singolo blog con l’id corrispondente fornito nei parametri dell’URL.
  • POST /api/blog/new: crea un nuovo blog nel database.
  • PUT /api/blog/:id: Aggiorna un blog con l’id corrispondente fornito nei parametri dell’URL.
  • DELETE /api/blog/:id: Elimina un blog con l’id corrispondente fornito nei parametri dell’URL.

Creare un file router.ts nella cartella src e aggiungervi le seguenti importazioni.

import { Router, RouterContext } from "../deps.ts";
import blogRepository from "./blogRepository.ts";

Quindi, creare un’istanza di router, aggiungendovi il blocco di codice sottostante:

const router = new Router();

Per registrare i gestori del router, occorre collegarli all’istanza del router.

Ad esempio (GET /api/blogs):

router
  .get("/api/blogs", async (ctx: RouterContext<"/api/blogs">) => {
    const data = await blogRepository.getAllBlogs();
    console.log(data);

    //format data
    const allBlogs = data.rows.map((blog) => {
      return {
        id: blog[0],
        title: blog[1],
        body: blog[2],
        author: blog[3]
      };
    });

    ctx.response.body = allBlogs;
  })

Il blocco di codice precedente crea un gestore di rotta per GET /api/blogs, concatenando la logica del gestore all’istanza del router.

Incatenarli al metodo precedentemente concatenato per registrare il resto delle rotte. In questo modo:

GET /api/blog/:id:

.get("/api/blog/:id", async (ctx: RouterContext<"/api/blog/:id">) => {
    try {
      const data = await blogRepository.getBlogById(ctx.params.id);
      console.log(data);

      //format data
      const blog = data.rows.map((blog) => {
        return {
          id: blog[0],
          title: blog[1],
          body: blog[2],
          author: blog[3]
        };
      });

      ctx.response.body = blog;
    } catch (error) {
      ctx.response.status = 500;
      ctx.response.body = {
        msg: "Error getting blog",
        error,
      };
    }
  })

POST /api/blog/new

.post("/api/blog/new", async (ctx: RouterContext<"/api/blog/new">) => {
    const resBody = ctx.request.body();
    const blog = await resBody.value;

    if (!blog) {
      ctx.response.status = 400;
      ctx.response.body = { msg: "Invalid data. Please provide a valid blog." };
      return;
    }

    const { title, body, author } = blog;

    if (!(title && body && author)) {
      ctx.response.status = 400;
      ctx.response.body = {
        msg: "Title or description missing. Please provide a valid blog.",
      };
      return;
    }

    try {
      await blogRepository.createBlog(title, body, author);

      ctx.response.status = 201;
      ctx.response.body = {
        msg: "blog added successfully",
      };
    } catch (error) {
      ctx.response.status = 500;
      ctx.response.body = {
        msg: "Error adding blog",
        error,
      };
    }
  })

PUT /api/blog/:id:

.put("/api/blog/:id", async (ctx: RouterContext<"/api/blog/:id">) => {
    try {
      const resBody = ctx.request.body();
      const blog = await resBody.value;

      if (!blog) {
        ctx.response.status = 400;
        ctx.response.body = {
          msg: "Invalid data. Please provide a valid blog.",
        };
        return;
      }

      const { title, body, author } = blog;

      if (!(title && body && author)) {
        ctx.response.status = 400;
        ctx.response.body = {
          msg: "Title or description missing. Please provide a valid blog.",
        };

        return;
      }

      await blogRepository.updateBlog(ctx.params.id, title, body, author);

      ctx.response.status = 200;
      ctx.response.body = {
        msg: "blog updated successfully",
      };
    } catch (error) {
      console.log(error);
      ctx.response.status = 500;
      ctx.response.body = {
        msg: "Error updating blog",
        error: error.message,
      };
    }
  })

DELETE /api/blog/:id:

.delete("/api/blog/:id", async (ctx: RouterContext<"/api/blog/:id">) => {
    await blogRepository.deleteBlog(ctx.params.id);

    ctx.response.status = 200;
    ctx.response.body = {
      msg: "blog deleted successfully",
    };
  });

Quindi, esportare l’istanza del router. In questo modo:

export default router;

Infine, modificare il file server.ts per creare un database del blog al primo avvio dell’applicazione.

Così:

import { Application } from "../deps.ts";
import config from "../config/default.ts";
import blogRepository from "./blogRepository.ts";

import router from "./router.ts";

const app = new Application();

(async () => {
  await blogRepository.createBlogTable();
})();

app.use(router.routes());
app.use(router.allowedMethods());

await app.listen({ port: config.port });

Il codice modificato aggiunge al file server.ts un IIFE che crea una nuova tabella di blog nel database.

Distribuzione dell’applicazione Deno su contenitori Back4app

Per distribuire l’applicazione Deno sui container Back4app, è necessario seguire i passaggi indicati di seguito:

Passo 1: Creare un file Docker

Un Dockerfile fornisce istruzioni specifiche per la creazione di un’immagine Docker. Queste istruzioni guidano il processo di costruzione dell’immagine.

Eseguite il comando seguente per creare un file Docker:

touch Dockerfile

Il comando precedente crea un file Docker nella cartella principale del progetto.

Quindi, aggiungete il blocco di codice sottostante al vostro file Docker:

FROM denoland/deno:latest

EXPOSE 8000

WORKDIR /app

COPY deps.ts .
RUN deno cache deps.ts

COPY . .

RUN deno cache src/server.ts

CMD ["run", "--allow-net", "--allow-env", "--allow-read", "src/server.ts"]

Il file Docker qui sopra configura un ambiente containerizzato per l’esecuzione di un’applicazione Deno. Mette in cache le dipendenze dell’applicazione e il punto di ingresso, quindi esegue l’applicazione Deno con i permessi specificati all’avvio del contenitore.

Ci si aspetta che l’applicazione ascolti sulla porta 8000, anche se la mappatura effettiva della porta deve essere fatta durante l’esecuzione del contenitore.

Infine, inviate il codice a GitHub.

Passo 2: Creare una nuova applicazione Back4app

Per creare un’applicazione Back4app, visitate il sito web ufficiale di Back4app. Una volta lì, individuare il pulsante Iscriviti nell’angolo in alto a destra della pagina di destinazione di Back4app. Facendo clic sul pulsante Iscriviti, si verrà indirizzati a un modulo di registrazione. Procedere alla compilazione del modulo con i dati richiesti, come l’indirizzo e-mail, il nome utente e la password. Assicuratevi di fornire informazioni accurate. Dopo aver completato il modulo, inviarlo.

Se si dispone già di un account, fare clic su Accedi.

Dopo aver configurato con successo il vostro account Back4app, effettuate il login per accedere al cruscotto del vostro account. Da qui, individuare il pulsante “NUOVA APP” e fare clic su di esso.

Questa azione vi indirizzerà a una pagina in cui vi verranno presentate diverse opzioni per la creazione della vostra nuova applicazione. Poiché l’intenzione è quella di distribuire utilizzando la containerizzazione, scegliere l’opzione“Containers as a Service“.

Quindi, collegare l’account GitHub all’account Back4app. Potete dare a Back4app l’accesso a tutti i repository del vostro account o a repository specifici.

Scegliere l’applicazione che si desidera distribuire, in questo caso l’applicazione costruita in questa esercitazione, e fare clic su Seleziona.

Scegliere il repository da distribuire

Facendo clic sul pulsante di selezione si accede a una pagina in cui vengono richieste alcune informazioni sulla propria applicazione, come il nome, il ramo, la directory principale, le opzioni di auto-deploy, la porta, la salute e le variabili ambientali.

Assicurarsi di fornire tutte le variabili ambientali necessarie per il corretto funzionamento dell’applicazione. Dopo aver completato la compilazione delle informazioni richieste, fare clic sul pulsante “Crea applicazione”.

Compilare i dati dell'applicazione

Questo avvierà il processo di distribuzione e dopo un po’ la distribuzione dovrebbe essere pronta. Se il processo di distribuzione richiede molto tempo, è possibile controllare i registri per verificare se si è verificato un errore nella distribuzione o consultare la guida alla risoluzione dei problemi di Back4app.

Conclusione

In questo articolo abbiamo esplorato Deno, i suoi vantaggi, le sue limitazioni, le opzioni di distribuzione più diffuse, come costruire un’applicazione con Deno e distribuire l’applicazione utilizzando i container Back4app.

Nonostante le sue limitazioni, grazie al suo modello di sicurezza, Deno può essere ideale per la creazione di applicazioni molto sicure e sensibili. Inoltre, il suo supporto nativo per TypeScript elimina i problemi di impostazione di TypeScript nel progetto.

Seguendo i passaggi illustrati in questo articolo è possibile creare e distribuire facilmente la propria applicazione Deno sui container Back4app.

FAQ

Che cos’è Deno?

Deno è un runtime JavaScript/TypeScript sicuro e moderno che consente agli sviluppatori di creare applicazioni lato server e lato client.

Come distribuire un’applicazione Deno?

1. Crea un’app Deno
2. Crea un Dockerfile
3. Invia la tua app Deno a GitHub e collega il tuo account GitHub al tuo account Back4app.
4. Crea un’app CaaS su Back4app
5. Seleziona la tua app Deno dall’elenco dei repository
6. Distribuisci la tua app Deno


Leave a reply

Your email address will not be published.