Framework Web pour Clojurescript sur le nœud.

Dans la tradition de Django, Flask et Rails. Conçu pour les développeurs indépendants qui expédient rapidement. Battle a été testé sur de vrais sites.
Philosophie | Démarrage rapide | Documentation | API | Exemples | Communauté
( ns webserver
( :require
[promesa.core :as p]
[sitefox.html :refer [render]]
[sitefox.web :as web]))
( defn root [_req res]
( ->> ( render [ :h1 " Hello world! " ])
( .send res)))
( p/let [[app host port] ( web/start )]
( .get app " / " root)
( print " Serving on " ( str " http:// " host " : " port)))PORT - Configurer le serveur Web de port SiteFox se lie.BIND_ADDRESS - Configurer l'adresse IP SiteFox Web Server se lie.SMTP_SERVER - Configurez le serveur SMTP sortant par exemple SMTP_SERVER=smtps://username:[email protected]/?pool=true .DATABASE_URL - Configurez la base de données vers laquelle vous connecter. Par défaut est sqlite://./database.sqlite . La façon la plus rapide de démarrer est d'utiliser l'un des scripts create qui mettra en place un exemple de projet pour vous avec une commande. Si vous créez un site Web simple sans trop d'interactivité frontale au-delà de la soumission de formulaire, le script NBB Create est le moyen:
npm init sitefox-nbb mywebsite
Cela créera un dossier appelé mywebsite contenant votre nouveau projet. Remarque Vous pouvez utiliser Scittle pour exécuter le côté client CLJS.
Si vous construisez une application Clojurescript complète, le script de création de l'ombre est le moyen:
npm init sitefox-shadow-fullstack myapp
Cela créera un dossier appelé myapp contenant votre nouveau projet.
Ajoutez SITEFOX à votre projet en tant que dépendance:
{:deps
{io.github.chr15m/sitefox {:git/tag "v0.0.26" :git/sha "e6ea2027b5d4277917732d43d550083c8e105da9"}}}
Si vous utilisez npm , vous pouvez installer SiteFox comme dépendance de cette façon. Si vous faites cela, vous devrez ajouter node_modules/sitefox/src à votre ClassPath en quelque sorte.
npm i sitefox
Remarque : les utilisateurs M1 Mac peuvent avoir besoin de définir la version Python dans NPM comme ceci:
npm config set python python3
En effet, la version node-sqlite3 échoue parfois sans le réglage. Voir ce problème pour plus de détails.
Un exemple de serveur avec deux itinéraires, dont l'un écrit des valeurs à la base de données de valeurs clés.
( ns my.server
( :require
[promesa.core :as p]
[sitefox.web :as web]
[sitefox.db :refer [kv]]))
( defn home-page [req res]
; send a basic hello world response
( .send res " Hello world! " ))
( defn hello [req res]
; write a value to the key-value database
( p/let [table ( kv " sometable " )
r ( .write table " key " 42 )]
( .json res true )))
( defn setup-routes [app]
; flush all routes from express
( web/reset-routes app)
; set up an express route for "/"
( .get app " / " home-page)
; set up an express route for "/hello"
( .post app " /hello " hello)
; statically serve files from the "public" dir on "/"
; (or from "build" dir in PROD mode)
( web/static-folder app " / " " public " ))
( defn main! []
; create an express server and start serving
; BIND_ADDRESS & PORT env vars set host & port.
( p/let [[app _host _port] ( web/start )]
; set up the routes for the first time
( setup-routes app)))Plus d'exemples Sitefox ici.
Si vous avez besoin de support avec SiteFox, vous pouvez:
SiteFox utilise le serveur Web express avec des défauts sensibles pour les sessions et la journalisation. Voir la documentation de routage express pour plus de détails.
Créez un nouveau serveur avec web/start et configurez un itinéraire qui répond avec "Hello World!" comme suit:
( -> ( web/start )
( .then ( fn [app host port]
( .get app " /myroute "
( fn [req res]
( .send res " Hello world! " )))) SiteFox est livré avec un système facultatif pour recharger les routes lorsque le serveur est modifié. Vos itinéraires Express seront rechargés chaque fois que votre code de serveur est actualisé (par exemple par une version Shadow-CLJS). Dans cet exemple, la fonction setup-routes sera appelée lorsqu'une reconstruction se produit.
( defn setup-routes [app]
; flush all routes from express
( web/reset-routes app)
; ask express to handle the route "/"
( .get app " / " ( fn [req res] ( .send res " Hello world! " ))))
; during the server setup hook up the reloader
( reloader ( partial #'setup-routes app)) Je recommande la bibliothèque Promesa pour gérer le flux de contrôle des promesses. Cet exemple suppose que [promesa.core :as p] :
( p/let [[app host port] ( web/start )]
; now use express `app` to set up routes and middleware
)Voir également ces exemples:
Au lieu de modèles, SiteFox propose des raccourcis pour le rendu des réactifs côté serveur, fusionné WTH HTML DOCUMENTS.
[sitefox.html :refer [render-into]]Vous pouvez charger un document HTML et rendre les formulaires de réactif dans un élément sélectionné:
( def index-html ( fs/readFileSync " index.html " ))
( defn component-main []
[ :div
[ :h1 " Hello world! " ]
[ :p " This is my content. " ]])
; this returns a new HTML string that can be returned
; e.g. with (.send res)
( render-into index-html " main " [component-main])SITEFOX utilise Node-HTML-Parser et propose des raccourcis pour travailler avec HTML et réactif:
html/parse est un raccourci pour node-html-parser/parse .html/render est un sténographie pour render-to-static-markup .html/$ est un raccourci pour le querySelector de l'analyseur.html/$$ est un raccourci pour le querySelectorAll de l'analyseur.Voir également l'exemple des modèles.
SiteFox facilite le démarrage de stockage des données de valeur clé sans configuration. Vous pouvez passer à des données plus structurées plus tard si vous en avez besoin. Il regroupe KeyV qui est une boutique de valeurs de clé soutenue de base de données. Vous pouvez accéder au magasin de valeurs de clé via db/kv et la base de données sous-jacente via db/client .
Voir la documentation complète du module DB.
Par défaut, une base de données SQLite locale est utilisée et vous pouvez commencer à persister des données sur le serveur immédiatement sans aucune configuration. Une fois que vous passez à la production, vous pouvez configurer une autre base de données à l'aide de la variable d'environnement DATABASE_URL . Par exemple, pour utiliser une base de données Postgres appelée "dbname", vous pouvez y accéder comme suit (en fonction de votre configuration réseau / local):
DATABASE_URL="postgres://%2Fvar%2Frun%2Fpostgresql/DBNAME"
DATABASE_URL=postgres://someuser:somepassword@somehost:5432/DBNAME
DATABASE_URL=postgres:///somedatabase
Notez que vous devrez également npm install @keyv/postgres si vous souhaitez utiliser le backend Postgres.
Pour utiliser la base de données et l'interface de valeur clé, nécessitez d'abord le module de base de données:
[sitefox.db :as db] Vous pouvez maintenant utiliser db/kv pour écrire une valeur de clé sur une "table".
( let [table ( db/kv " sometable " )]
( .set table " key " " 42 " ))Récupérez à nouveau la valeur:
( -> ( .get table " key " )
( .then ( fn [val] ( print val)))) Vous pouvez utiliser db/client pour accéder au client de base de données sous-jacent. Par exemple pour faire une requête par rapport à la base de données configurée:
( let [c ( db/client )]
( -> ( .query c " select * from sometable WHERE x = 1 " )
( .then ( fn [rows] ( print rows)))))Encore une fois, Promesa est recommandée pour gérer le flux de contrôle pendant les opérations de base de données.
Pour explorer les données de valeur clé de la ligne de commande, utilisez SQLite et JQ pour filtrer les données comme celle-ci:
sqlite3 database.sqlite "select * from keyv where key like 'SOMEPREFIX%';" | cut -f 2 -d "|" | jq '.'
Par défaut, le module node-sqlite3 ne fournit pas de traces de pile complète avec des numéros de ligne, etc. Lorsqu'une erreur de base de données se produit. Il est possible d'activer les traces de pile verbeuse avec une petite pénalité de performance comme suit:
( ns yourapp
( :require
[ " sqlite3 " :as sqlite3]))
( .verbose sqlite3) Si vous souhaitez exécuter SQLite3 en production, vous pouvez rencontrer l'erreur SQLITE_BUSY: database is locked lors de l'exécution des opérations de base de données simultanées à partir de différents clients. Il est possible de résoudre ces problèmes de concurrence et de verrouillage en activant le mode de journalisation de l'écriture dans SQLite3 comme suit:
(ns yourapp
(:require
[sitefox.db :refer [client]]))
(p/let [c (client)
wal-mode-enabled (.query c "PRAGMA journal_mode=WAL;")]
(js/console.log wal-mode-enabled))
Ce code peut être placé en toute sécurité dans la fonction principale de votre code de serveur.
Les sessions sont activées par défaut et chaque visiteur de votre serveur aura sa propre session. Les données de session sont du côté du serveur persistant entre les charges de page afin que vous puissiez les utiliser pour stocker l'état d'authentification par exemple. Les sessions sont étayées dans une table kv à comptation (voir la section de la base de données ci-dessus). Vous pouvez lire et écrire des structures de données JS arbitraires à la session à l'aide de req.session .
Pour écrire une valeur à la boutique de session (à l'intérieur d'une fonction de gestionnaire de routes):
( let [session ( aget req " session " )]
( aset session " myvalue " 42 ))Pour lire une valeur dans le magasin de session:
( aget req " session " " myvalue " )SiteFox enveloppe la bibliothèque Passport pour implémenter l'authentification. Vous pouvez ajouter une authentification simple et basée sur un mot de passe à votre application avec trois appels de fonction:
( defn setup-routes [app]
( let [template ( fs/readFileSync " index.html " )]
( web/reset-routes app)
; three calls to set up email based authentication
( auth/setup-auth app)
( auth/setup-email-based-auth app template " main " )
( auth/setup-reset-password app template " main " )
; ... add your additional routes here ... ;
)) La chaîne template passé est un document HTML et "main" est le sélecteur spécifiant où monter l'interface utilisateur AUTH. Cela configurera les itinéraires suivants par défaut où vous pouvez envoyer des utilisateurs pour vous inscrire, vous connecter et réinitialiser leur mot de passe:
/auth/sign-in/auth/sign-up/auth/reset-passwordIl est également possible de remplacer les formulaires de réactif UI Auth par défaut et les URL de redirection pour les personnaliser avec vos propres versions. Voir la documentation Auth pour savoir comment fournir vos propres formulaires de réactif. Voir également le code source des formulaires d'authentification par défaut de réactif si vous souhaitez créer le vôtre.
Lorsqu'un utilisateur s'inscrit, ses données sont persistées dans la base de données KEYV par défaut utilisée par SiteFox. Vous pouvez récupérer la datastructure de l'utilisateur actuellement authentifié sur l'objet de demande:
( let [user ( aget req " user " )] ...) Vous pouvez ensuite mettre à jour les données de l'utilisateur et enregistrer ses données dans la base de données. La bibliothèque applied-science.js-interop est pratique pour cela (requise ici comme j ):
( p/let [user ( aget req " user " )]
( j/assoc! user :somekey 42 )
( auth/save-user user)) Si vous souhaitez créer une nouvelle table, il est utile de le clés sur l'UUID de l'utilisateur avec lequel vous pouvez obtenir (aget user "id") .
Voir l'exemple d'authentification pour plus de détails.
Pour ajouter un nouveau schéma d'authentification tel que le nom d'utilisateur basé sur le nom d'utilisateur, ou OAuth tiers, consultez le Passport Docs et Auth.ClJS. Tirez les demandes les plus bienvenues!
SITEFOX regroupe le NODEMAILLER pour avoir envoyé des e-mails. Configurez votre serveur SMTP sortant:
SMTP_SERVER=smtps://username:[email protected]/?pool=true
Ensuite, vous pouvez utiliser la fonction send-email comme suit:
( -> ( mail/send-email
" [email protected] "
" [email protected] "
" This is my test email. "
:text " Hello, This is my first email from **Sitefox**. Thank you. " )
( .then js/console.log)) Par défaut, les e-mails envoyés sont enregistrés au format ./logs/mail.log au format JSON-lines.
Si vous ne spécifiez pas de serveur SMTP, le module de messagerie sera en mode débogage. Aucun e-mail ne sera envoyé, des e-mails sortants seront écrits à /tmp pour inspection, et les résultats send-email seront également enregistrés à la console.
Si vous définissez SMTP_SERVER=ethereal le service eThereal.Email sera utilisé. Après avoir exécuté send-email vous pouvez imprimer la propriété url du résultat. Vous pouvez utiliser les liens imprimés pour tester vos e-mails en mode Dev.
Voir également l'exemple de Send-Email.
Voir l'exemple de validation du formulaire qui utilise le validateur de nœud-input et vérifie les problèmes CSRF.
Pour vous assurer que vous pouvez POST sans avertissements CSRF, vous devez créer un élément caché comme celui-ci (syntaxe réactive):
[ :input { :name " _csrf " :type " hidden " :default-value ( .csrfToken req)}] Si vous faites une demande POST ajax du côté client, vous devez passer le jeton CSRF en tant qu'en-tête. Un jeton valide est disponible en tant que chaîne au point de terminaison JSON /_csrf-token et vous pouvez le récupérer en utilisant fetch-csrf-token et l'ajouter aux en-têtes d'une demande de fetch comme suit:
( ns n ( :require [sitefox.ui :refer [fetch-csrf-token]]))
( -> ( fetch-csrf-token )
( .then ( fn [token]
( js/fetch " /api/endpoint "
#js { :method " POST "
:headers #js { :Content-Type " application/json "
:X-XSRF-TOKEN token} ; <- use token here
:body ( js/JSON.stringify ( clj->js some-data))})))) Remarque : vous pouvez plutôt récupérer le jeton CSRF à partir d'un cookie côté client si vous définissez la variable d'environnement SEND_CSRF_TOKEN . C'était la valeur par défaut dans les versions SiteFox précédentes. Lorsqu'il est défini, SiteFox enverra le jeton à chaque demande de GET dans le cookie du client XSRF-TOKEN et cela peut être récupéré avec la fonction ui/csrf-token . Il s'agit d'une forme valide mais moins sûre de protection du CSRF.
Dans certaines circonstances rares, vous souhaiterez peut-être désactiver les chèques CSRF (par exemple en publiant une API à partir d'un appareil non naval). Si vous savez ce que vous faites, vous pouvez utiliser le pre-csrf-router pour ajouter des itinéraires qui contournent la vérification du CSRF:
( defn setup-routes [app]
; flush all routes from express
( web/reset-routes app)
; set up an API route bypassing CSRF checks
( .post ( j/get app " pre-csrf-router " ) " /api/endpoint " endpoint-unprotected-by-csrf)
; set up an express route for "/hello" which is protected as normal
( .post app " /hello " hello)) Par défaut, le serveur Web écrira dans les fichiers journaliers dans le dossier ./logs . Ces fichiers sont automatiquement tournés par le serveur. Il existe deux types de journaux:
logs/access.log qui sont des journaux d'accès Web standard au format "combiné".logs/error.log où les tracebacks sont écrits à l'aide tracebacks/install-traceback-handler .Pour envoyer des exceptions non revues au journal d'erreur:
(def admin-email (env-required "ADMIN_EMAIL"))
(def build-id (try (fs/readFileSync "build-id.txt") (catch :default _e "dev")))
(install-traceback-handler admin-email build-id)
Créer build-id.txt basé sur le commit GIT actuel comme suit:
git rev-parse HEAD | cut -b -8 > build-id.txt
Si vous souhaitez obtenir des numéros de ligne Clojurescript corrects dans les tracebacks, ["source-maps-support" :as sourcemaps] puis:
(.install sourcemaps)
Vous pouvez utiliser la fonction web/setup-error-handler pour servir une page pour les erreurs en fonction d'un composant réactif que vous définissez:
( defn component-error-page [_req error-code error]
[ :section.error
[ :h2 error-code " Error " ]
( case error-code
404 [ :p " We couldn't find the page you're looking for. " ]
500 [ :<> [ :p " An error occurred: " ] [ :p ( .toString error)]]
[ :div " An unknown error occurred. " ])])
( web/setup-error-handler app my-html-template " main " component-error-page)Vous pouvez les combiner pour attraper les 500 erreurs de serveur interne et les exceptions non apprises comme suit:
(let [traceback-handler (install-traceback-handler admin-email build-id)]
(web/setup-error-handler app template-app "main" component-error-page traceback-handler))
Le rechargement en direct est pris en charge à l'aide nbb et shadow-cljs . Il est activé par défaut lors de l'utilisation des scripts de création de NPM. Les exemples ont plus de détails.
SITEFOX a été fabriqué par Chris McCormick (@MCCRMX sur Twitter et @ chris @ McCormick.cx sur Mastodon). J'ai itéré dessus tout en construisant des sites pour moi-même et pour les clients.