Citizen ist ein MVC-basiertes Webanwendungs-Framework, das für Personen entwickelt wurde, die schnell schnelle, skalierbare Websites erstellen möchten, anstatt sich mit Knotens Eingeweide umzugraben oder einen wackeligen Jenga-Turm aus 50 verschiedenen Paketen zusammenzubilden.
Verwenden Sie Citizen als Grundlage für eine traditionelle serverseitige Webanwendung, eine modulare einseitige Anwendung (SPA) oder eine erholsame API.
Der Übergang von 0.9.x auf 1,0.x. Bitte konsultieren Sie den ChangeLog für eine aufgeführte Liste und überprüfen Sie diese aktualisierte Dokumentation gründlich.
Dies ist eindeutig mehr Inhalt, als jeder NPM/Github Readme enthalten sollte. Ich arbeite an einer Website für diese Dokumentation.
Ich benutze Bürger auf meiner persönlichen Website und Originaltrilogy.com. OT.com kümmert sich um eine moderate Menge an Verkehr (einige hunderttausend Ansichten pro Monat) für einen Cloud -Hosting -Plan von 30 US -Dollar, der eine einzige Instanz von Bürger ausführt, in der die App/der Prozess monatelang ohne Absturz läuft. Es ist sehr stabil.
Diese Befehle erstellen ein neues Verzeichnis für Ihre Web -App, installieren Bürger, verwenden das Gerüstdienstprogramm, um das Skelett der App zu erstellen und den Webserver zu starten:
$ mkdir myapp && cd myapp
$ npm install citizen
$ node node_modules/citizen/util/scaffold skeleton
$ node app/start.jsWenn alles gut lief, werden Sie in der Konsole die Bestätigung sehen, dass der Webserver ausgeführt wird. Gehen Sie in Ihrem Browser zu http://127.0.0.1:3000, und Sie werden eine nackte Indexvorlage sehen.
Citizen verwendet Vorlagenliterale in seiner Standard -Vorlage -Engine. Sie können Konsolidierung installieren, die Vorlagenkonfiguration aktualisieren und die Standardansichtsvorlagen entsprechend ändern.
Für Konfigurationsoptionen siehe Konfiguration. Weitere Dienstprogramme, die Ihnen beim Einstieg helfen, finden Sie die Versorgungsunternehmen.
app/
config/ // These files are all optional
citizen.json // Default config file
local.json // Examples of environment configs
qa.json
prod.json
controllers/
hooks/ // Application event hooks (optional)
application.js
request.js
response.js
session.js
routes/ // Public route controllers
index.js
helpers/ // Utility modules (optional)
models/ // Models (optional)
index.js
views/
error/ // Default error views
404.html
500.html
ENOENT.html
error.html
index.html // Default index view
start.js
logs/ // Log files
access.log
error.log
web/ // public static assets
Importieren Sie Bürger und starten Sie Ihre App:
// start.js
import citizen from 'citizen'
global . app = citizen
app . start ( )Aus dem Terminal laufen:
$ node start.jsSie können Ihre Citizen -App mit einer Konfigurationsdatei, Startoptionen und/oder benutzerdefinierten Controller -Konfigurationen konfigurieren.
Das Konfigurationsverzeichnis ist optional und enthält Konfigurationsdateien im JSON -Format, die sowohl den Bürger als auch Ihre App vorantreiben. In diesem Verzeichnis können Sie mehrere Citizen -Konfigurationsdateien haben, die unterschiedliche Konfigurationen basierend auf der Umgebung ermöglichen. Der Bürger baut seine Konfiguration basierend auf der folgenden Hierarchie auf:
host -Schlüssel sucht, der dem Hostnamen des Geräts entspricht. Wenn sie einen findet, erweitert sie die Standardkonfiguration mit der Dateikonfiguration.host -Schlüssel finden kann, sucht er nach einer Datei namens Citizen.json und lädt diese Konfiguration.Angenommen, Sie möchten Bürger in Port 8080 in Ihrer lokalen Entwicklerumgebung leiten, und Sie haben eine lokale Datenbank, mit der Ihre App eine Verbindung herstellt. Sie können eine Konfigurationsdatei namens Local.json (oder dev.json, was auch immer Sie wollen) mit Folgendes erstellen:
{
"host" : "My-MacBook-Pro.local" ,
"citizen" : {
"mode" : "development" ,
"http" : {
"port" : 8080
}
} ,
"db" : {
"server" : "localhost" , // app.config.db.server
"username" : "dbuser" , // app.config.db.username
"password" : "dbpassword" // app.config.db.password
}
}Diese Konfiguration würde die Standardkonfiguration nur beim Ausführen auf Ihrem lokalen Computer erweitern. Mit dieser Methode können Sie mehrere Konfigurationsdateien aus verschiedenen Umgebungen zu demselben Repository begehen.
Die Datenbankeinstellungen sind in Ihrer App über app.config.db überall in Ihrer App zugegriffen. Die citizen und host sind dem Rahmen reserviert. Erstellen Sie Ihre eigenen Knoten, um Ihre benutzerdefinierten Einstellungen zu speichern.
Sie können die Konfiguration Ihrer App über app.start() beim Start einstellen. Wenn es eine Konfigurationsdatei gibt, erweitert die Startkonfiguration die Konfigurationsdatei. Wenn es keine Konfigurationsdatei gibt, erweitert die Startkonfiguration die Standard -Citizen -Konfiguration.
// Start an HTTPS server with a PFX file
app . start ( {
citizen : {
http : {
enabled : false
} ,
https : {
enabled : true ,
pfx : '/absolute/path/to/site.pfx'
}
}
} ) Um benutzerdefinierte Konfigurationen auf der Ebene der Routencontroller festzulegen, exportieren Sie ein config (mehr auf Routencontrollern und Aktionen im Abschnitt "Routencontroller).
export const config = {
// The "controller" property sets a configuration for all actions in this controller
controller : {
contentTypes : [ 'application/json' ]
}
// The "submit" property is only for the submit() controller action
submit : {
form : {
maxPayloadSize : 1000000
}
}
} Das Folgende stellt die Standardkonfiguration des Bürgers dar, die durch Ihre Konfiguration erweitert wird:
{
host : '' ,
citizen : {
mode : process . env . NODE_ENV || 'production' ,
global : 'app' ,
http : {
enabled : true ,
hostname : '127.0.0.1' ,
port : 80
} ,
https : {
enabled : false ,
hostname : '127.0.0.1' ,
port : 443 ,
secureCookies : true
} ,
connectionQueue : null ,
templateEngine : 'templateLiterals' ,
compression : {
enabled : false ,
force : false ,
mimeTypes : [
'application/javascript' ,
'application/x-javascript' ,
'application/xml' ,
'application/xml+rss' ,
'image/svg+xml' ,
'text/css' ,
'text/html' ,
'text/javascript' ,
'text/plain' ,
'text/xml'
]
} ,
sessions : {
enabled : false ,
lifespan : 20 // minutes
} ,
layout : {
controller : '' ,
view : ''
} ,
contentTypes : [
'text/html' ,
'text/plain' ,
'application/json' ,
'application/javascript'
] ,
forms : {
enabled : true ,
maxPayloadSize : 524288 // 0.5MB
} ,
cache : {
application : {
enabled : true ,
lifespan : 15 , // minutes
resetOnAccess : true ,
encoding : 'utf-8' ,
synchronous : false
} ,
static : {
enabled : false ,
lifespan : 15 , // minutes
resetOnAccess : true
} ,
invalidUrlParams : 'warn' ,
control : { }
} ,
errors : 'capture' ,
logs : {
access : false , // performance-intensive, opt-in only
error : {
client : true , // 400 errors
server : true // 500 errors
} ,
debug : false ,
maxFileSize : 10000 ,
watcher : {
interval : 60000
}
} ,
development : {
debug : {
scope : {
config : true ,
context : true ,
cookie : true ,
form : true ,
payload : true ,
route : true ,
session : true ,
url : true ,
} ,
depth : 4 ,
showHidden : false ,
view : false
} ,
watcher : {
custom : [ ] ,
killSession : false ,
ignored : / (^|[/\]).. / // Ignore dotfiles
}
} ,
urlPath : '/' ,
directories : {
app : < appDirectory > ,
controllers : < appDirectory > + '/controllers',
helpers : < appDirectory > + '/helpers',
models : < appDirectory > + '/models',
views : < appDirectory > + '/views',
logs : new URL('../../../logs', import.meta.url).pathname
web : new URL('../../../web', import.meta.url).pathname
}
}
} Hier ist ein vollständiger Überblick über die Einstellungen des Bürgers und was sie tun.
Wenn Sie einen Server starten, können Sie zusätzlich zu den http und https -Konfigurationsoptionen von Citizen die gleichen Optionen wie HTTP.CreateServer () und https.createServer () von Node angeben.
Der einzige Unterschied besteht darin, wie Sie Schlüsseldateien übergeben. Wie Sie in den obigen Beispielen sehen können, übergeben Sie den Bürger die Dateipfade für Ihre Schlüsseldateien. Citizen liest die Dateien für Sie.
| Einstellung | Typ | Standardwert | Beschreibung |
|---|---|---|---|
host | Saite | '' | Um verschiedene Konfigurationsdateien in verschiedenen Umgebungen zu laden, verlässt sich der Bürger auf den Hostnamen des Servers als Schlüssel. Wenn der Bürger bei Startup eine Konfigurationsdatei mit einem host -Schlüssel findet, der mit dem Hostnamen des Servers entspricht, wählt sie diese Konfigurationsdatei aus. Dies ist nicht mit dem HTTP -Server hostname zu verwechseln (siehe unten). |
| Bürger | |||
mode | Saite | Überprüft zuerst NODE_ENV , ansonsten production | Der Anwendungsmodus bestimmt bestimmte Laufzeitverhalten. Mögliche Werte sind production und development zum Schweigen von Konsolenprotokollen. Der Entwicklungsmodus ermöglicht ausführliche Konsolenprotokolle, URL -Debug -Optionen und den Austausch von Hot -Modul. |
global | Saite | app | Die Konvention zur Initialisierung von Bürgern in der Startdatei weist dem Framework einer globalen Variablen zu. Der Standardwert, auf den in der gesamten Dokumentation verwiesen wird, ist app . Sie können diese Einstellung ändern, wenn Sie einen anderen Namen verwenden möchten. |
contentTypes | Array | [ 'text/html', 'text/plain', 'application/json', 'application/javascript' ] | Eine Zulassungsliste der Antwortformate für jede Anfrage, basierend auf dem Accept -Anforderungsheader des Kunden. Bei der Konfiguration der verfügbaren Formate für einzelne Routencontroller oder Aktionen muss das gesamte Array der verfügbaren Formate bereitgestellt werden. |
errors | Saite | capture | Wenn Ihre Anwendung einen Fehler verursacht, besteht das Standardverhalten darin, dass die Bürger versuchen, sich von dem Fehler zu wiederholen und die Anwendung zu halten. Wenn Sie diese Option zum exit einstellen, werden den Bürger angezeigt, den Fehler zu protokollieren und stattdessen den Vorgang zu beenden. |
templateEngine | Saite | templateLiterals | Citizen verwendet [Vorlage buchstäblich] (https://developer.mozilla.org/en-us/docs/web/javascript/reference/template_literals) Syntax für die Ansicht standardmäßig. Optional können Sie Konsolidierung installieren und alle Motors verwenden (z. B. Installieren von Lenker und templateEngine auf handlebars einstellen). |
urlPath | Saite | / | Bezeichnet den URL -Pfad, der zu Ihrer App führt. Wenn Sie möchten, dass Ihre App über http://yourSite.com/my/app zugegriffen wird und Sie keinen anderen Server als Front -End zur Proxy der Anforderung verwenden, sollte diese Einstellung /my/app sein (vergessen Sie nicht den führenden Schrägstrich). Diese Einstellung ist erforderlich, damit der Router funktioniert. |
| http | |||
enabled | Boolean | true | Aktiviert den HTTP -Server. |
hostname | Saite | 127.0.0.1 | Der Hostname, auf den Ihre App über http zugegriffen werden kann. Sie können eine leere Zeichenfolge angeben, um Anfragen bei jedem Hostnamen anzunehmen. |
port | Nummer | 3000 | Die Portnummer, auf der der HTTP -Server des Bürgers auf Anforderungen hört. |
| https | |||
enabled | Boolean | false | Aktiviert den HTTPS -Server. |
hostname | Saite | 127.0.0.1 | Der Hostname, auf den Ihre App über HTTPS zugegriffen werden kann. Die Standardeinstellung ist Localhost, aber Sie können eine leere Zeichenfolge angeben, um Anforderungen bei jedem Hostnamen zu akzeptieren. |
port | Nummer | 443 | Die Portnummer, auf der der HTTPS -Server des Bürgers auf Anforderungen hört. |
secureCookies | Boolean | true | Standardmäßig sind alle in einer HTTPS -Anforderung festgelegten Cookies sicher. Legen Sie diese Option auf false fest, um dieses Verhalten zu überschreiben, alle Cookies unsicher zu machen und Sie müssen die secure Option in der Cookie -Anweisung manuell festlegen. |
connectionQueue | Ganze Zahl | null | Die maximale Anzahl eingehender Anfragen zur Warteschlange. Wenn das Betriebssystem nicht spezifiziert wird, bestimmt es die Warteschlange. |
| Sitzungen | |||
enabled | Boolean | false | Ermöglicht den Bereich der Benutzersitzung, das jedem Besucher eine eindeutige ID zuweist und Ihnen ermöglicht, Daten zu speichern, die dieser ID innerhalb des Anwendungsservers zugeordnet sind. |
lifespan | Positive Ganzzahl | 20 | Wenn Sitzungen aktiviert sind, repräsentiert diese Zahl die Länge der Sitzung eines Benutzers in Minuten. Sitzungen verfallen automatisch, wenn ein Benutzer in dieser Zeit inaktiv war. |
| Layout | |||
controller | Saite | '' | Wenn Sie einen globalen Layout -Controller verwenden, können Sie hier den Namen dieses Controllers angeben, anstatt die next Anweisung in allen Ihren Controllern zu verwenden. |
view | Saite | '' | Standardmäßig verwendet der Layout -Controller die Standard -Layout -Ansicht, aber Sie können hier eine andere Ansicht angeben. Verwenden Sie den Dateinamen ohne Dateierweiterung. |
| Formen | |||
enabled | Boolean | true | Citizen bietet grundlegende Nutzlastverarbeitung für einfache Formulare. Wenn Sie es vorziehen, ein separates Formularpaket zu verwenden, setzen Sie dies auf false . |
maxPayloadSize | Positive Ganzzahl | 524288 | Maximale Formularnutzlastgröße in Bytes. Legen Sie eine maximale Nutzlastgröße fest, um zu verhindern, dass Ihr Server über Formulareingabedaten überlastet wird. |
| Kompression | |||
enabled | Boolean | false | Ermöglicht die Komprimierung von GZIP und Deflate für gerenderte Ansichten und statische Vermögenswerte. |
force | Boolesche oder String | false | Kürzt die Gzip- oder Deflate -Codierung für alle Kunden, auch wenn sie nicht berichten, dass komprimierte Formate akzeptiert werden. Viele Proxys und Firewalls brechen den Akzept-kodierenden Header, der die GZIP-Unterstützung bestimmt, und da alle modernen Kunden Gzip unterstützen, ist es normalerweise sicher, ihn zu erzwingen, indem Sie diese auf gzip setzen, aber Sie können auch deflate erzwingen. |
mimeTypes | Array | Siehe Standardkonfiguration oben. | Eine Reihe von MIME -Typen, die beim Aktivieren der Komprimierung komprimiert werden. In der obigen Beispielkonfiguration finden Sie die Standardliste. Wenn Sie Elemente hinzufügen oder entfernen möchten, müssen Sie das Array in seiner Gesamtheit ersetzen. |
| Cache | |||
control | Objekt, das Schlüssel-/Wertpaare enthält | {} | Verwenden Sie diese Einstellung, um Cache-Kontroll-Header für Routencontroller und statische Vermögenswerte festzulegen. Der Schlüssel ist der Pfadname des Vermögenswerts, und der Wert ist der Cache-Kontroll-Header. Weitere Informationen finden Sie unter dem Client-Side-Caching. |
invalidUrlParams | Saite | warn | Die Option Routen-Cache kann gültige URL-Parameter angeben, um zu verhindern, dass schlechte URLs zwischengespeichert werden, und invalidUrlParams bestimmt, ob eine Warnung bei der Begegnung mit schlechten URLs oder einem Client-Seite-Fehler protokolliert werden soll. Weitere Informationen finden Sie unter Caching -Anfragen und Controller -Aktionen. |
| Cache.Application | |||
enabled | Boolean | true | Aktiviert den In-Memory-Cache, auf den über die Methoden cache.set() und cache.get() zugegriffen wird. |
lifespan | Nummer | 15 | Die Zeitdauer, die ein zwischengespeicherter Anwendungsvermögen in Minuten im Speicher bleibt. |
resetOnAccess | Boolean | true | Bestimmt, ob der Cache -Timer auf einem zwischengespeicherten Vermögenswert zurückgesetzt werden soll, wenn der Cache zugegriffen wird. Wenn auf false eingestellt wird, verfallen zwischengespeicherte Gegenstände, wenn die lifespan erreicht ist. |
encoding | Saite | utf-8 | Wenn Sie einen Dateipfad an cache.set () übergeben, bestimmt die Codierungseinstellung, welche Codierung beim Lesen der Datei verwendet werden sollte. |
synchronous | Boolean | false | Wenn Sie einen Dateipfad an cache.set () übergeben, bestimmt diese Einstellung, ob die Datei synchron oder asynchron gelesen werden soll. Standardmäßig sind die Dateilesungen asynchron. |
| cache.static | |||
enabled | Boolean | false | Beim Servieren statische Dateien liest der Bürger die Datei normalerweise für jede Anfrage von der Festplatte. Sie können die statische Datei beschleunigen, die erheblich serviert werden, indem Sie dies auf true einstellen, was die Dateipuffer im Speicher zwischengespeichert. |
lifespan | Nummer | 15 | Die Zeitdauer, die ein zwischengespeichertes statisches Vermögenswert in den Minuten bleibt. |
resetOnAccess | Boolean | true | Bestimmt, ob der Cache -Timer auf einem zwischengespeicherten statischen Vermögenswert zurückgesetzt werden soll, wenn der Cache zugegriffen wird. Wenn auf false eingestellt wird, verfallen zwischengespeicherte Gegenstände, wenn die lifespan erreicht ist. |
| Protokolle | |||
access | Boolean | false | Aktiviert HTTP -Zugriffsprotokolldateien. Standardmäßig deaktiviert, da Zugriffsprotokolle schnell und idealerweise von einem Webserver behandelt werden können. |
debug | Boolean | false | Aktiviert Debug -Protokolldateien. Nützlich für die Debugie von Produktionsproblemen, aber extrem ausführlich (die gleichen Protokolle, die Sie in der Konsole im Entwicklungsmodus sehen würden). |
maxFileSize | Nummer | 10000 | Bestimmt die maximale Dateigröße von Protokolldateien in Kilobyte. Wenn das Limit erreicht ist, wird die Protokolldatei mit einem Zeitstempel umbenannt und eine neue Protokolldatei erstellt. |
| logs.Error | |||
client | Boolean | true | Ermöglicht die Protokollierung von Clientfehlern auf 400 Ebenen. |
server | Boolean | false | Ermöglicht die Protokollierung von Server-/Anwendungsfehlern auf 500 Ebenen. |
status | Boolean | false | Steuert, ob Statusnachrichten im Produktionsmodus an der Konsole angemeldet werden sollten. (Der Entwicklungsmodus meldet sich immer bei der Konsole an.) |
| logs.watcher | |||
interval | Nummer | 60000 | Für Betriebssysteme, die keine Dateiereignisse unterstützen, stellt dieser Timer fest, wie oft Protokolldateien vor der Archivierung in Millisekunden für Änderungen befragt werden. |
| Entwicklung | |||
| Entwicklung.Debug | |||
scope | Objekt | Diese Einstellung bestimmt, welche Bereiche im Debug -Ausgang im Entwicklungsmodus protokolliert sind. Standardmäßig sind alle Bereiche aktiviert. | |
depth | Positive Ganzzahl | 3 | Wenn Citizen ein Objekt in den Debug -Inhalt absetzt, wird es mit dem Node -Util.inspect inspiziert. Diese Einstellung bestimmt die Tiefe der Inspektion, dh die Anzahl der Knoten, die überprüft und angezeigt werden. Größere Zahlen bedeuten eine tiefere Inspektion und langsamere Leistung. |
view | Boolean | false | Stellen Sie dies auf True ein, um Debugginformationen direkt in die HTML -Ansicht zu entsorgen. |
enableCache | Boolean | false | Der Entwicklungsmodus deaktiviert den Cache. Ändern Sie diese Einstellung in true , um den Cache im Entwicklungsmodus zu aktivieren. |
| Entwicklung.watcher | |||
custom | Array | Sie können den Ersatz für das heiße Modul des Citizen erkennen, um Ihre eigenen benutzerdefinierten Module zu beobachten. Dieses Array kann Objekte mit watch (relativer Verzeichnispfad zu Ihren Modulen im App -Verzeichnis) enthalten und (die Variable, der Sie diese Module zuweisen) Eigenschaften assign . Beispiel:[ { "watch": "/util", "assign": "app.util" } ] | |
Citizen verwendet Chokidar als Dateibeobachter, sodass die Option watcher für Protokolle und Entwicklungsmodus auch jede von Chokidar zugelassene Option akzeptiert.
Diese Einstellungen werden öffentlich über app.config.host und app.config.citizen ausgesetzt.
In dieser Dokumentation geht davon aus, dass Ihr globaler App -Variable -Name app ist. Entsprechend einstellen.
app.start() | Startet einen Citizen Web Application Server. |
app.config | Die Konfigurationseinstellungen, die Sie beim Start angegeben haben. Die Einstellungen des Bürgers liegen in app.config.citizen . |
app.controllersapp.modelsapp.views | Es ist unwahrscheinlich, dass Sie direkt auf Controller und Ansichten zugreifen müssen. Verweisen Sie jedoch auf app.models , anstatt Ihre Modelle manuell vom integrierten Ersatz für den integrierten Hot-Modul des Bürgers zu importieren. |
app.helpers | Alle Helfer-/Versorgungsmodule in app/helpers/ werden in das Helfer -Objekt importiert. |
app.cache.set()app.cache.get()app.cache.exists()app.cache.clear() | Anwendungs -Cache und Schlüssel-/Wertspeicher, das intern von Citizen verwendet wird, auch für Ihre App verfügbar. |
app.log() | Grundlegende Konsolen- und Datei -Protokollierung von Bürger, die für Ihre Verwendung exportiert wurden. |
Die Citizen URL-Struktur bestimmt, welche Routencontroller und Aktion zum Feuer verabschiedet werden, über die URL-Parameter und ein bisschen Raum für SEO-freundliche Inhalte, die sich als eindeutige Kennung verdienen können. Die Struktur sieht so aus:
http://www.site.com/controller/seo-content/action/myAction/param/val/param2/val2
Nehmen wir zum Beispiel an, die Basis -URL Ihrer Website ist:
http://www.cleverna.me
Der Standard -Routencontroller ist index , und die Standardaktion ist handler() , sodass das oben genannte das Äquivalent der folgenden ist:
http://www.cleverna.me/index/action/handler
Wenn Sie einen article -Routencontroller haben, würden Sie ihn so anfordern:
http://www.cleverna.me/article
Anstelle von Abfragebrägern besteht der Bürger die URL -Parameter, die aus Namen/Wertpaaren bestehen. Wenn Sie eine Artikel -ID von 237 und eine Seitennummer von 2 übergeben mussten, würden Sie die URL Namen/Wertpaare anhängen:
http://www.cleverna.me/article/id/237/page/2
Gültige Parameternamen können Buchstaben, Zahlen, Unterstriche und Striche enthalten, müssen jedoch mit einem Buchstaben oder einem Unterstrich beginnen.
Die Standard -Controller -Aktion ist handler() , aber Sie können alternative Aktionen mit dem action (mehr dazu später) angeben:
http://www.cleverna.me/article/action/edit
Mit Citizen können Sie auch optional relevante Inhalte in Ihre URLs einfügen, wie dies:
http://www.cleverna.me/article/My-Clever-Article-Title/page/2
Dieser SEO -Inhalt muss immer dem Controller -Namen folgen und allen Namen/Wert -Paaren, einschließlich der Controller -Aktion, vorausgehen. url können über die route.descriptor generell url.article .
Die URL action und direct sind für das Framework reserviert. Verwenden Sie sie also nicht für Ihre App.
Bürger stützt sich auf eine einfache Modell-View-Controller-Konvention. Das oben erwähnte Artikelmuster kann die folgende Struktur verwenden:
app/
controllers/
routes/
article.js
models/
article.js // Optional, name it whatever you want
views/
article.html // The default view file name should match the controller name
Für eine bestimmte URL ist mindestens ein Routencontroller erforderlich, und die Standardansicht -Datei eines Routencontrollers muss ihren Namen teilen. Modelle sind optional.
Alle Ansichten für einen bestimmten Routencontroller können in app/views/ Verzeichnissen vorhanden sein oder in einem Verzeichnis platziert werden, dessen Name dem des Controllers für die sauberere Organisation übereinstimmt:
app/
controllers/
routes/
article.js
models/
article.js
views/
article/
article.html // The default view
edit.html // Alternate article views
delete.html
Weitere Ansichten im Abschnitt "Ansichten".
Modelle und Ansichten sind optional und müssen nicht unbedingt mit einem bestimmten Controller in Verbindung gebracht werden. Wenn Ihr Routencontroller seine Ausgabe zur weiteren Verarbeitung und endgültigen Rendering an einen anderen Controller übergeben will, müssen Sie keine Übereinstimmungsansicht angeben (siehe Controller Nächste Anweisung).
Ein Bürgerroutencontroller ist nur ein JavaScript -Modul. Jeder Routencontroller benötigt mindestens einen Export, um als Aktion für die angeforderte Route zu dienen. Die Standardaktion sollte als handler() bezeichnet werden, der von Bürger bezeichnet wird, wenn in der URL keine Aktion angegeben wird.
// Default route controller action
export const handler = async ( params , request , response , context ) => {
// Do some stuff
return {
// Send content and directives to the server
}
} Der Citizen Server ruft handler() auf, nachdem er die anfängliche Anforderung verarbeitet und 4 Argumente übergeben hat: Ein params -Objekt, das die Parameter der Anforderung, die Node.js request und response und den Kontext der aktuellen Anforderung enthält.
params -Objekts config | Die Konfiguration Ihrer App, einschließlich aller Anpassungen für die aktuelle Controller -Aktion |
route | Details der angeforderten Route wie der URL und des Namens des Routencontrollers |
url | Alle von der URL abgeleiteten Parameter |
form | Daten, die aus einem Beitrag gesammelt wurden |
payload | Die Nutzlast der Rohanforderung |
cookie | Cookies mit der Anfrage gesendet |
session | Sitzungsvariablen, wenn Sitzungen aktiviert sind |
Neben dem Zugriff auf diese Objekte in Ihrem Controller sind sie auch automatisch in Ihrem Ansichtskontext enthalten, sodass Sie sie in Ihren Ansichtsvorlagen als lokale Variablen verweisen können (weitere Details im Abschnitt "Ansichten).
Zum Beispiel basierend auf dem vorherigen Artikel URL ...
http://www.cleverna.me/article/My-Clever-Article-Title/id/237/page/2
... Sie haben das folgende params.url -Objekt an Ihren Controller übergeben:
{
article : 'My-Clever-Article-Title' ,
id : '237' ,
page : '2'
} Der Controller-Name wird zu einer Eigenschaft im URL-Bereich, die auf den Deskriptor verweist, was ihn für die Verwendung als eindeutige Kennung gut geeignet macht. Es ist auch im params.route -Objekt als params.route.descriptor verfügbar.
Das context enthält alle Daten oder Richtlinien, die von früheren Controllern in der Kette unter Verwendung ihrer return -Anweisung generiert wurden.
Um die Ergebnisse der Controller -Aktion zurückzugeben, geben Sie eine return mit allen Daten und Anweisungen an, die Sie an den Bürger weitergeben möchten.
Mit den obigen URL -Parametern kann ich den Artikelinhalt aus dem Modell abrufen und an den Server zurückgeben:
// article controller
export const handler = async ( params ) => {
// Get the article
const article = await app . models . article . get ( {
article : params . url . article ,
page : params . url . page
} )
const author = await app . models . article . getAuthor ( {
author : article . author
} )
// Any data you want available to the view should be placed in the local directive
return {
local : {
article : article ,
author : author
}
}
} Alternative Aktionen können unter Verwendung des action -URL -Parameters angefordert werden. Vielleicht möchten wir beispielsweise eine andere Aktion und eine andere Ansicht, um einen Artikel zu bearbeiten:
// http://www.cleverna.me/article/My-Clever-Article-Title/id/237/page/2/action/edit
// article controller
export const handler = async ( params ) => {
// Get the article
const article = await app . models . article . get ( {
article : params . url . article ,
page : params . url . page
} )
const author = await app . models . article . getAuthor ( {
author : article . author
} )
// Return the article for view rendering using the local directive
return {
local : {
article : article ,
author : author
}
}
}
export const edit = async ( params ) => {
// Get the article
const article = await app . models . article . get ( {
article : params . url . article ,
page : params . url . page
} )
// Use the /views/article/edit.html view for this action
return {
local : {
article : article
} ,
view : 'edit'
}
} Sie platzieren alle Daten, die Sie in der return an den Bürger weitergeben möchten. Alle Daten, die Sie Ihrer Sicht aufnehmen möchten, sollten in einem Objekt, das als local bezeichnet wird, wie oben gezeigt übergeben werden. Zusätzliche Objekte können an die Bürger weitergegeben werden, um Anweisungen festzulegen, die dem Server Anweisungen zur Verfügung stellen (siehe Controller -Anweisungen). Sie können sogar Ihre eigenen Objekte zum Kontext hinzufügen und sie vom Controller zum Controller übergeben (mehr im Abschnitt mit Controller -Ketten).
Modelle sind optionale Module und ihre Struktur liegt vollständig bei Ihnen. Der Bürger spricht nicht direkt mit Ihren Modellen. Es speichert sie nur in app.models für Ihre Bequemlichkeit. Sie können sie auch manuell in Ihre Controller importieren, wenn Sie es vorziehen.
Die folgende Funktion, wenn sie in app/models/article.js platziert wird, ist in Ihrer App über app.models.article.get() in Ihrer App zugegriffen:
// app.models.article.get()
export const get = async ( id ) => {
let article = // do some stuff to retrieve the article from the db using the provided ID, then...
return article
} Citizen verwendet Vorlagenliterale für die Ansichtsrelecting standardmäßig. Sie können consolidate.js installieren und jede unterstützte Vorlage -Engine verwenden. Aktualisieren Sie einfach die templateEngine -Konfigurationseinstellung entsprechend.
In article.html können Sie auf Variablen verweisen, die Sie in das local Objekt in die Return -Anweisung des Routencontrollers übergeben haben. Der Bürger injiziert auch Eigenschaften aus dem params -Objekt automatisch in Ihren Ansichtskontext, sodass Sie Zugriff auf diese Objekte als lokale Variablen (z. B. den url -Bereich) haben:
<!-- article.html -->
<!doctype html >
< html >
< body >
< main >
< h1 >
${local.article.title} — Page ${url.page}
</ h1 >
< h2 > ${local.author.name}, ${local.article.published} </ h2 >
< p >
${local.article.summary}
</ p >
< section >
${local.article.text}
</ section >
</ main >
</ body >
</ html > Standardmäßig macht der Server die Ansicht, deren Name dem des Controllers übereinstimmt. Um eine andere Ansicht zu machen, verwenden Sie die view in Ihrer Rückgabeerklärung.
Alle Ansichten gehen in /app/views . Wenn ein Controller mehrere Ansichten hat, können Sie diese in einem nach diesem Controller benannten Verzeichnis organisieren.
app/
controllers/
routes/
article.js
index.js
views/
article/
article.html // Default article controller view
edit.html
index.html // Default index controller view
Sie können einen Routencontroller angeben, seine lokalen Variablen als JSON oder JSON-P zurückzugeben, indem Sie den entsprechenden Accept -Header in Ihrer Anfrage einstellen, sodass dieselbe Ressource sowohl eine vollständige HTML-Ansicht als auch JSON für AJAX-Anfragen und RESTful-APIs bedienen kann.
Die Artikel -Route Controller handler() Aktion würde zurückkehren:
{
"article" : {
"title" : " My Clever Article Title " ,
"summary" : " Am I not terribly clever? " ,
"text" : " This is my article text. "
},
"author" : {
"name" : " John Smith " ,
"email" : " [email protected] "
}
} Was auch immer Sie zur Rückgabeanweisung des Controller hinzugefügt haben local wird zurückgegeben.
Verwenden Sie für JSONP callback in der URL:
http://www.cleverna.me/article/My-Clever-Article-Title/callback/foo
Rückgaben:
foo ( {
"article" : {
"title" : "My Clever Article Title" ,
"summary" : "Am I not terribly clever?" ,
"text" : "This is my article text."
} ,
"author" : {
"name" : "John Smith" ,
"email" : "[email protected]"
}
} ) ; Um einen bestimmten Inhaltstyp für eine bestimmte Anforderung zu erzwingen, setzen Sie response.contentType fest.
export const handler = async ( params , request , response ) => {
// Every request will receive a JSON response regardless of the Accept header
response . contentType = 'application/json'
}Sie können einen globalen Antworttyp über alle Anfragen innerhalb eines Ereignishakens erzwingen.
Helfer sind optionale Versorgungsmodule und ihre Struktur liegt vollständig bei Ihnen. Sie werden in app.helpers für Ihre Bequemlichkeit gespeichert. Sie können sie auch manuell in Ihre Controller und Modelle importieren, wenn Sie es vorziehen.
Die folgende Funktion ist in app/helpers/validate.js in Ihrer App über app.helpers.validate.email() in Ihrer App zugegriffen:
// app.helpers.validate.email()
export const email = ( address ) => {
const emailRegex = new RegExp ( / [a-z0-9!##$%&''*+/=?^_`{|}~-]+(?:.[a-z0-9!##$%&''*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])? / i )
return emailRegex . test ( address )
} Citizen speichert alle Module im app -Bereich nicht nur zum einfachen Abrufen, sondern auch zum Ersetzen des Hot Moduls (HMR). Wenn Sie Änderungen an einem Modul oder einer Ansicht im Entwicklungsmodus speichern, löscht Citizen das vorhandene Modul-Import und implementiert diese Modul in Echtzeit erneut.
Sie sehen ein Konsolenprotokoll, in dem die betroffene Datei feststellt, und Ihre App wird weiter ausgeführt. Keine Notwendigkeit, neu zu starten.
Citizen tut sein Bestes, um Fehler anmutig zu behandeln, ohne den Prozess zu verlassen. Die folgende Controller -Aktion wirft einen Fehler auf, der Server antwortet jedoch mit 500 und wird weiter ausgeführt:
export const handler = async ( params ) => {
// app.models.article.foo() doesn't exist, so this action will throw an error
const foo = await app . models . article . foo ( params . url . article )
return {
local : foo
}
}Sie können auch einen Fehler manuell werfen und die Fehlermeldung anpassen:
export const handler = async ( params ) => {
// Get the article
const article = await app . models . article . get ( {
article : params . url . article ,
page : params . url . page
} )
// If the article exists, return it
if ( article ) {
return {
local : {
article : article
}
}
// If the article doesn't exist, throw a 404
} else {
// Error messages default to the standard HTTP Status Code response, but you can customize them.
let err = new Error ( 'The requested article does not exist.' )
// The HTTP status code defaults to 500, but you can specify your own
err . statusCode = 404
throw err
}
} Beachten Sie, dass params.route.controller vom angeforderten Controller zu error aktualisiert wird. Daher sollten alle Referenzen in Ihrer App auf den angeforderten Controller dies berücksichtigen.
Fehler werden in dem von der Route angeforderten Format zurückgegeben. Wenn Sie JSON anfordern und die Route einen Fehler auswirkt, gibt der Bürger den Fehler im JSON -Format zurück.
Das vom Dienstprogramm des Gerüstdienstprogramms erstellte App -Skeletts -Skelett enthält optionale Fehleransichtsvorlagen für gemeinsame Client- und Serverfehler. Sie können jedoch Vorlagen für jeden HTTP -Fehlercode erstellen.
Die Standard -Fehlerbehandlungsmethode des Bürgers ist capture , die eine anmutige Genesung versucht. Wenn Sie den Vorgang nach einem Fehler bevorzugen, exit Sie config.citizen.errors .
// config file: exit the process after an error
{
"citizen" : {
"errors" : "exit"
}
}Nachdem der Anwendungsfehler -Handler ausgelöst wird, verlässt der Bürger den Prozess.
Um benutzerdefinierte Fehleransichten für Serverfehler zu erstellen, erstellen Sie ein Verzeichnis namens /app/views/error und füllen Sie es mit Vorlagen, die nach dem HTTP -Antwortcode oder dem Node -Fehlercode benannt sind.
app/
views/
error/
500.html // Displays any 500-level error
404.html // Displays 404 errors specifically
ENOENT.html // Displays bad file read operations
error.html // Displays any error without its own template
Zusätzlich zu den Ansichtsdaten kann die Return -Anweisung der Route Controller -Aktion auch Anweisungen übergeben, um alternative Ansichten zu rendern, Cookies und Sitzungsvariablen festzulegen, Weiterleitungen zu initiieren, anzurufen und zu rendern, die Routencontrolleraktionen/Ansichten (oder ganze Anforderungen) und die Anforderung an eine andere Controller zur weiteren Verarbeitung weitergeben.
Standardmäßig macht der Server die Ansicht, deren Name dem des Controllers übereinstimmt. Um eine andere Ansicht zu machen, verwenden Sie die view in Ihrer Rückgabeerklärung:
// article controller
export const edit = async ( params ) => {
const article = await app . models . article . get ( {
article : params . url . article ,
page : params . url . page
} )
return {
local : article ,
// This tells the server to render app/views/article/edit.html
view : 'edit'
}
} Sie setzen Cookies, indem Sie ein cookie -Objekt in die Controller -Aktion zurückgeben.
export const handler = async ( params ) => {
return {
cookie : {
// Cookie shorthand sets a cookie called username using the default cookie properties
username : params . form . username ,
// Sets a cookie called last_active that expires in 20 minutes
last_active : {
value : new Date ( ) . toISOString ( ) ,
expires : 20
}
}
}
}Hier ist ein Beispiel für die Standardeinstellungen eines vollständigen Cookie -Objekts:
myCookie = {
value : 'myValue' ,
// Valid expiration options are:
// 'now' - deletes an existing cookie
// 'never' - current time plus 30 years, so effectively never
// 'session' - expires at the end of the browser session (default)
// [time in minutes] - expires this many minutes from now
expires : 'session' ,
path : '/' ,
// citizen's cookies are accessible via HTTP/HTTPS only by default. To access a
// cookie via JavaScript, set this to false.
httpOnly : true ,
// Cookies are insecure when set over HTTP and secure when set over HTTPS.
// You can override that behavior globally with the https.secureCookies setting
// in your config or on a case-by-case basis with this setting.
secure : false
} Sobald Cookies auf dem Client eingestellt sind, sind sie in params.cookie innerhalb von Controllern und einfach cookie in der Ansicht erhältlich:
<!doctype html >
< html >
< body >
< section >
Welcome, ${cookie.username}.
</ section >
</ body >
</ html > Cookie -Variablen, die Sie in Ihrem Controller festgelegt haben, sind in den params.cookie -Bereiche nicht sofort verfügbar. Der Bürger muss den Kontext vom Controller erhalten und die Antwort zuerst an den Kunden senden. Verwenden Sie daher eine lokale Instanz der Variablen, wenn Sie während derselben Anfrage darauf zugreifen müssen.
Alle von Bürger festgelegten Kekse beginnen mit dem Präfix ctzn_ um Kollisionen zu vermeiden. Starten Sie Ihre Cookie -Namen nicht mit ctzn_ und Sie sollten keine Probleme haben.
Wenn Sie Bürger hinter einem Proxy wie Nginx oder Apache verwenden, stellen Sie sicher, dass Sie in Ihrer Serverkonfiguration einen HTTP -Header Forwarded , sodass die Handhabung sicherer Cookies durch die Bürger korrekt funktioniert.
Hier ist ein Beispiel dafür, wie Sie dies in Nginx einrichten könnten:
location / {
proxy_set_header Forwarded "for=$remote_addr;host=$host;proto=$scheme;";
proxy_pass http://127.0.0.1:8080;
}
Wenn Sitzungen aktiviert sind, können Sie über params.session in Ihrem Controller oder einfach session innerhalb von Ansichten auf Sitzungsvariablen zugreifen. Diese lokalen Scopes verweisen auf die Sitzung des aktuellen Benutzers, ohne eine Sitzungs -ID übergeben zu müssen.
Standardmäßig hat eine Sitzung vier Eigenschaften: id , started , expires und timer . Die Sitzungs -ID wird auch als Cookie namens ctzn_session_id an den Client gesendet.
Das Einstellen von Sitzungsvariablen ist so gut wie das Einstellen von Cookie -Variablen:
return {
session : {
username : 'Danny' ,
nickname : 'Doc'
}
} Wie bei Cookies sind Sitzungsvariablen, die Sie gerade zugewiesen haben, nicht während derselben Anforderung innerhalb des params.session -Bereichs verfügbar. Verwenden Sie daher eine lokale Instanz, wenn Sie sofort auf diese Daten zugreifen müssen.
Die Sitzungen verfallen basierend auf den sessions.lifespan der Sitzungen. Die Standardeinstellung beträgt 20 Minuten. Der timer wird mit jeder Anfrage vom Benutzer zurückgesetzt. Wenn der timer ausgeht, wird die Sitzung gelöscht. Alle Client -Anfragen generieren nach dieser Zeit eine neue Sitzungs -ID und senden einen neuen Sitzungs -ID -Cookie an den Client.
Um die Sitzung des aktuellen Benutzers gewaltsam zu klären und abzufallen:
return {
session : {
expires : 'now'
}
} Alle von den Bürger festgelegten Sitzungsvariablen beginnen mit dem Präfix ctzn_ um Kollisionen zu vermeiden. Starten Sie Ihre Sitzungsvariablennamen nicht mit ctzn_ und Sie sollten keine Probleme haben.
Sie können Umleitungsanweisungen an den Server weitergeben, der nach der Verarbeitung der Controller -Aktion eingeleitet wird.
Das redirect nimmt eine URL -Zeichenfolge in seine Kurzhotelemperatur oder drei Optionen ein: statusCode , url und refresh . Wenn Sie keinen Statuscode angeben, verwendet Citizen 302 (vorübergehende Umleitung). Die refresh bestimmt, ob die Umleitung einen Standort-Header oder den nicht standardmäßigen Refresh-Header verwendet.
// Initiate a temporary redirect using the Location header
return {
redirect : '/login'
}
// Initiate a permanent redirect using the Refresh header, delaying the redirect by 5 seconds
return {
redirect : {
url : '/new-url' ,
statusCode : 301 ,
refresh : 5
}
} Im Gegensatz zum Standort-Header sendet Citizen, wenn Sie die Option refresh verwenden, eine gerenderte Ansicht an den Kunden, da die Umleitung eine kundenseitige Seite erfolgt.
Unter Verwendung der Standort -Header bricht (meiner Meinung nach) der Referer -Header, da der Referator nicht die Ressource ist, die die Umleitung initiierte, sondern die Ressource vor der Seite, die sie initiierte. Um dieses Problem zu umgehen, speichert Citizen eine Sitzungsvariable namens ctzn_referer , die die URL der Ressource enthält, die die Umleitung initiierte, mit der Sie Benutzer ordnungsgemäß umleiten können. Wenn ein nicht authentifizierter Benutzer beispielsweise versucht, auf eine sichere Seite zuzugreifen und sie in ein Anmeldeformular umzuleiten, wird die Adresse der sicheren Seite in ctzn_referer gespeichert, damit Sie sie anstelle der vorherigen Seite dorthin senden können.
Wenn Sie keine Sitzungen aktiviert haben, fällt Citizen stattdessen auf die Erstellung eines Cookie namens ctzn_referer zurück.
Wenn Sie Bürger hinter einem Proxy wie Nginx oder Apache verwenden, stellen Sie sicher, dass Sie in Ihrer Serverkonfiguration einen HTTP Forwarded in Ihre Serverkonfiguration haben, damit ctzn_referer ordnungsgemäß funktioniert.
Hier ist ein Beispiel dafür, wie Sie dies in Nginx einrichten könnten:
location / {
proxy_set_header Forwarded "for=$remote_addr;host=$host;proto=$scheme;";
proxy_pass http://127.0.0.1:8080;
}
Sie können HTTP -Header mit der header -Anweisung einstellen:
return {
header : {
'Cache-Control' : 'max-age=86400' ,
'Date' : new Date ( ) . toISOString ( )
}
} header können auch Header direkt mit response.setHeader() des Knotens festlegen.
Mit Citizen können Sie vollständige MVC -Muster verwenden, wie dies inklusive die Bürgerversion von Komponenten ist. Jeder hat einen eigenen Routencontroller, ein Modell und seine Ansichten. Inklusive kann verwendet werden, um eine Aktion auszuführen oder eine vollständige gerenderte Ansicht zurückzugeben. Jeder Routencontroller kann ein inklusive sein.
Nehmen wir an, die Vorlage unseres Artikelsmusters hat den folgenden Inhalt. Der Kopfabschnitt enthält dynamische Meta -Daten, und der Inhalt des Headers ändert sich, je nachdem, ob der Benutzer angemeldet ist oder nicht:
<!doctype html >
< html >
< head >
< title > ${local.metaData.title} </ title >
< meta name =" description " content =" ${local.metaData.description} " >
< meta name =" keywords " content =" ${local.metaData.keywords} " >
< link rel =" stylesheet " type =" text/css " href =" site.css " >
</ head >
< body >
< header >
${ cookie.username ? ' < p > Welcome, ' + cookie.username + ' </ p > ' : ' < a href =" /login " > Login </ a > ' }
</ header >
< main >
< h1 > ${local.article.title} — Page ${url.page} </ h1 >
< p > ${local.article.summary} </ p >
< section > ${local.article.text} </ section >
</ main >
</ body >
</ html >Es ist wahrscheinlich sinnvoll zu verwenden, für den Kopfabschnitt und den Header zu verwenden, da Sie diesen Code überall verwenden, aber anstelle von einfachen Teilern können Sie Bürger einschließen. Der Kopfabschnitt kann ein eigenes Modell zur Bevölkerung der Meta -Daten verwenden. Da der Header für authentifizierte Benutzer unterschiedlich ist, ziehen wir diese Logik aus der Ansicht heraus und stecken Sie es in den Controller des Headers. Ich gehe gerne der Konvention zu, Partials mit einem Unterstrich zu beginnen, aber das liegt an Ihnen:
app/
controllers/
routes/
_head.js
_header.js
article.js
models/
_head.js
article.js
views/
_head.html
_header/
_header.html
_header-authenticated.html // A different header for logged in users
article.html
Wenn der Artikelcontroller abgefeuert wird, muss er den Bürger mitteilen, der ihn benötigt. Wir machen das mit der Richtlinie include :
// article controller
export const handler = async ( params ) => {
// Get the article
const article = await app . models . article . get ( {
article : params . url . article ,
page : params . url . page
} )
return {
local : {
article : article
} ,
include : {
// Include shorthand is a string containing the pathname to the desired route controller
_head : '/_head/action/article' ,
// Long-form include notation can explicitly define a route controller, action, and view
_header : {
controller : '_header' ,
// If the username cookie exists, use the authenticated action. If not, use the default action.
action : params . cookie . username ? 'authenticated' : 'handler'
}
}
}
} In den Bürger werden Muster eingeschlossen, die die gleichen Anforderungen haben wie reguläre Muster, einschließlich eines Controllers mit öffentlicher Aktion. Die obige include zeigt den Bürger, die _head und _header -Controller aufzurufen, ihnen dieselben Argumente zu übergeben, die an den article -Controller übergeben wurden (Paramien, Anfrage, Antwort, Kontext), ihre jeweiligen Ansichten übertragen und die resultierenden Ansichten zum Ansichtskontext hinzufügen.
So könnte unser Kopf -Abschnitt -Controller aussehen:
// _head controller
export const article = async ( params ) => {
let metaData = await app . models . _head ( { article : params . url . article } )
return {
local : {
metaData : metaData
}
}
}Und die Ansicht der Kopfabteilung:
< head >
< title > ${local.metaData.title} </ title >
< meta name =" description " content =" ${local.metaData.description} " >
< meta name =" keywords " content =" ${local.metaData.keywords} " >
< link rel =" stylesheet " type =" text/css " href =" site.css " >
</ head >So könnte unser Header -Controller aussehen:
// _header controller
// No need for a return statement, and no need to specify the view
// because handler() renders the default view.
//
// Every route controller needs at least one action, even if it's empty.
export const handler = ( ) => { }
export const authenticated = ( ) => {
return {
view : '_header-authenticated'
}
}Und die Kopfzeileansichten:
<!-- /views/_header/_header.html -->
< header >
< a href =" /login " > Login </ a >
</ header > <!-- /views/_header/_header-authenticated.html -->
< header >
< p > Welcome, ${cookie.username} </ p >
</ header > Die gerenderten Includen werden im include -Bereich gespeichert:
<!-- /views/article.html -->
<!doctype html >
< html >
${include._head}
< body >
${include._header}
< main >
< h1 > ${local.title} — Page ${url.page} </ h1 >
< p > ${local.summary} </ p >
< section > ${local.text} </ section >
</ main >
</ body >
</ html >Die Einschlüsse der Bürger sind in sich geschlossen und werden als vollständige Ansicht an den Anrufcontroller übertragen. Während sie dieselben Daten (URL -Parameter, Formulareingänge, Anforderungskontext usw.) wie der aufrufende Controller erhalten, werden Daten, die in einem Einfügen generiert werden, nicht an den Anrufer weitergeleitet.
Auf ein Muster, das als Einfügen verwendet werden soll, kann über HTTP wie jeder andere Routencontroller über HTTP zugegriffen werden. Sie können den _header -Controller wie so anfordern und einen Stück HTML oder JSON als Antwort erhalten:
http://cleverna.me/_header
Dies eignet sich hervorragend für die Bearbeitung des ersten Anforderungs-Server-Side-Side-Side-Side-Side-Side-Side-Side-Side-Side-Side-Side-Side-Side-Unternehmens und der Aktualisierung von Inhalten mit einer clientseitigen Bibliothek.
Der Bürger schließt umfassende Funktionen zur Verfügung, haben jedoch Einschränkungen und können in bestimmten Situationen übertrieben werden.
Mit Citizen können Sie mehrere Routencontroller aus einer einzigen Anforderung mit der next Anweisung zusammenstellen. Der angeforderte Controller übergibt seine Daten und wird an einen nachfolgenden Controller angezeigt, fügt seine eigenen Daten hinzu und rendert seine eigene Ansicht.
Sie können so viele Routencontroller in einer einzigen Anfrage zusammenstellen, wie Sie möchten. In jedem Routencontroller wird seine Daten- und Ansichtsausgabe in den params.route.chain -Objekt gespeichert.
// The index controller accepts the initial request and hands off execution to the article controller
export const handler = async ( params ) => {
let user = await app . models . user . getUser ( { userID : params . url . userID } )
return {
local : {
user : user
} ,
// Shorthand for next is a string containing the pathname to the route controller.
// URL paramaters in this route will be parsed and handed to the next controller.
next : '/article/My-Article/id/5'
// Or, you can be explicit, but without parameters
next : {
// Pass this request to app/controllers/routes/article.js
controller : 'article' ,
// Specifying the action is optional. The next controller will use its default action, handler(), unless you specify a different action here.
action : 'handler' ,
// Specifying the view is optional. The next controller will use its default view unless you tell it to use a different one.
view : 'article'
}
// You can also pass custom directives and data.
doSomething: true
}
}Each controller in the chain has access to the previous controller's context and views. The last controller in the chain provides the final rendered view. A layout controller with all your site's global elements is a common use for this.
// The article controller does its thing, then hands off execution to the _layout controller
export const handler = async ( params , request , response , context ) => {
let article = await getArticle ( { id : params . url . id } )
// The context from the previous controller is available to you in the current controller.
if ( context . doSomething ) { // Or, params.route.chain.index.context
await doSomething ( )
}
return {
local : {
article : article
} ,
next : '/_layout'
}
} The rendered view of each controller in the chain is stored in the route.chain object:
<!-- index.html, which is stored in route.chain.index.output -->
< h1 > Welcome, ${local.user.username}! </ h1 >
<!-- article.html, which is stored in route.chain.article.output -->
< h1 > ${local.article.title} </ h1 >
< p > ${local.article.summary} </ p >
< section > ${local.article.text} </ section >The layout controller handles the includes and renders its own view. Because it's the last controller in the chain, this rendered view is what will be sent to the client.
// _layout controller
export const handler = async ( params ) => {
return {
include : {
_head : '/_head' ,
_header : {
controller : '_header' ,
action : params . cookie . username ? 'authenticated' : 'handler'
} ,
_footer : '/_footer
}
}
} <!-- _layout.html -->
<!doctype html >
< html >
${include._head}
< body >
${include._header}
< main >
<!-- You can render each controller's view explicitly -->
${route.chain.index.output}
${route.chain.article.output}
<!-- Or, you can loop over the route.chain object to output the view from each controller in the chain -->
${Object.keys(route.chain).map( controller = > { return route.chain[controller].output }).join('')}
</ main >
${include._footer}
</ body >
</ html > You can skip rendering a controller's view in the chain by setting the view directive to false:
// This controller action won't render a view
export const handler = async ( ) => {
return {
view : false ,
next : '/_layout'
}
} To bypass next in a request, add /direct/true to the URL.
http://cleverna.me/index/direct/true
The requested route controller's next directive will be ignored and its view will be returned to the client directly.
As mentioned in the config section at the beginning of this document, you can specify a default layout controller in your config so you don't have to insert it at the end of every controller chain:
{
"citizen" : {
"layout" : {
"controller" : " _layout " ,
"view" : " _layout "
}
}
} If you use this method, there's no need to use next for the layout. The last controller in the chain will always hand the request to the layout controller for final rendering.
citizen provides several ways for you to improve your app's performance, most of which come at the cost of system resources (memory or CPU).
In many cases, a requested URL or route controller action will generate the same view every time based on the same input parameters, so it doesn't make sense to run the controller chain and render the view from scratch for each request. citizen provides flexible caching capabilities to speed up your server side rendering via the cache directive.
If a given request (URL) will result in the exact same rendered view with every request, you can cache that request with the request property. This is the fastest cache option because it pulls a fully rendered view from memory and skips all controller processing.
Let's say you chain the index, article, and layout controllers like we did above. If you put the following cache directive in your index controller, the requested URL's response will be cached and subsequent requests will skip the index, article, and layout controllers entirely.
return {
next : '/article' ,
cache : {
request : true
}
}For the request cache directive to work, it must be placed in the first controller in the chain; in other words, the original requested route controller (index in this case). It will be ignored in any subsequent controllers.
The URL serves as the cache key, so each of the following URLs would generate its own cache item:
http://cleverna.me/article
http://cleverna.me/article/My-Article
http://cleverna.me/article/My-Article/page/2
The example above is shorthand for default cache settings. The cache.request directive can also be an object with options:
// Cache the requested route with some additional options
return {
cache : {
request : {
// Optional. This setting lets the server respond with a 304 Not Modified
// status if the cache content hasn't been updated since the client last
// accessed the route. Defaults to the current time if not specified.
lastModified : new Date ( ) . toISOString ( ) ,
// Optional. List of valid URL parameters that protects against accidental
// caching of malformed URLs.
urlParams : [ 'article' , 'page' ] ,
// Optional. Life of cached item in minutes. Default is 15 minutes.
// For no expiration, set to 'application'.
lifespan : 15 ,
// Optional. Reset the cached item's expiration timer whenever the item is
// accessed, keeping it in the cache until traffic subsides. Default is true.
resetOnAccess : true
}
}
} If a given route chain will vary across requests, you can still cache individual controller actions to speed up rendering using the action property.
// Cache this controller action using the default settings
return {
cache : {
action : true
}
}
// Cache this controller with additional options
return {
cache : {
action : {
// These options function the same as request caching (see above)
urlParams : [ 'article' , 'page' ] ,
lifespan : 15 ,
resetOnAccess : true
}
}
}When you cache controller actions, their context is also cached. Setting a cookie or session variable in a cached controller action means all future requests for that action will set the same cookie or session variable—probably not something you want to do with user data.
lastModified This setting lets the server respond with a faster 304 Not Modified response if the content of the request cache hasn't changed since the client last accessed it. By default, it's set to the time at which the request was cached, but you can specify a custom date in ISO format that reflects the last modification to the request's content.
return {
next : '/_layout' ,
cache : {
request : {
// Use toISOString() to format your date appropriately
lastModified : myDate . toISOString ( ) // 2015-03-05T08:59:51.491Z
}
}
} urlParams The urlParams property helps protect against invalid cache items (or worse: an attack meant to flood your server's resources by overloading the cache).
return {
next : '/_layout' ,
cache : {
request : {
urlParams : [ 'article' , 'page' ]
}
}
}If we used the example above in our article controller, the following URLs would be cached because the "article" and "page" URL parameters are permitted:
http://cleverna.me/article
http://cleverna.me/article/My-Article-Title
http://cleverna.me/article/My-Article-Title/page/2
The following URLs wouldn't be cached, which is a good thing because it wouldn't take long for an attacker's script to loop over a URL and flood the cache:
http://cleverna.me/article/My-Article-Title/dosattack/1
http://cleverna.me/article/My-Article-Title/dosattack/2
http://cleverna.me/article/My-Article-Title/page/2/dosattack/3
The server logs a warning when invalid URL parameters are present, but continues processing without caching the result.
lifespanThis setting determines how long the request or controller action should remain in the cache, in minutes.
return {
cache : {
request : {
// This cached request will expire in 10 minutes
lifespan : 10
}
}
} resetOnAccess Used with the lifespan setting, resetOnAccess will reset the timer of the route or controller cache whenever it's accessed, keeping it in the cache until traffic subsides. Defaults to true .
return {
cache : {
request : {
// This cached request will expire in 10 minutes, but if a request accesses it
// before then, the cache timer will be reset to 10 minutes from now
lifespan : 10 ,
resetOnAccess : true
}
}
} In most cases, you'll probably want to choose between caching an entire request (URL) or caching individual controller actions, but not both.
When caching an include controller action, the route pathname pointing to that include is used as the cache key. If you use logic to render different views using the same controller action, the first rendered view will be cached. You can pass an additional URL parameter in such cases to get past this limitation and create a unique cache item for different include views.
export const handler = async ( context ) => {
return : {
// Two different versions of the _header include will be cached becaues the URLs are unique
include : context . authenticated ? '/_header/authenticated/true' : '/_header'
}
} citizen's cache is a RAM cache stored in the V8 heap, so be careful with your caching strategy. Use the lifespan and resetOnAccess options so URLs that receive a lot of traffic stay in the cache, while less popular URLs naturally fall out of the cache over time.
By caching static assets in memory, you speed up file serving considerably. To enable static asset caching for your app's public (web) directory, set cache.static.enabled to true in your config:
{
"citizen" : {
"cache" : {
"static" : {
"enabled" : true
}
}
}
}citizen handles response headers automatically (ETags, 304 status codes, etc.) using each file's last modified date. Note that if a file changes after it's been cached, you'll need to clear the file cache using cache.clear() or restart the app.
To clear a file from the cache in a running app:
app . cache . clear ( { file : '/absolute/path/to/file.jpg' } )With static caching enabled, all static files citizen serves will be cached in the V8 heap, so keep an eye on your app's memory usage to make sure you're not using too many resources.
citizen automatically sets ETag headers for cached requests and static assets. You don't need to do anything to make them work. The Cache-Control header is entirely manual, however.
To set the Cache-Control header for static assets, use the cache.control setting in your config:
{
"citizen" : {
"cache" : {
"static" : true ,
"control" : {
"/css/global.css" : " max-age=86400 " ,
"/css/index.css" : " max-age=86400 " ,
"/js/global.js" : " max-age=86400 " ,
"/js/index.js" : " max-age=86400 " ,
"/images/logo.png" : " max-age=31536000 "
}
}
}
} The key name is the pathname that points to the static asset in your web directory. If your app's URL path is /my/app , then this value should be something like /my/app/styles.css . The value is the Cache-Control header value you want to assign to that asset.
You can use strings that match the exact pathname like above, or you can also use wildcards. Mixing the two is fine:
{
"citizen" : {
"cache" : {
"static" : true ,
"control" : {
"/css/*" : " max-age=86400 " ,
"/js/*" : " max-age=86400 " ,
"/images/logo.png" : " max-age=31536000 "
}
}
}
}Here's a great tutorial on client-side caching to help explain ETag and Cache-Control headers.
Both dynamic routes and static assets can be compressed before sending them to the browser. To enable compression for clients that support it:
{
"citizen" : {
"compression" : {
"enabled" : true
}
}
}Proxies, firewalls, and other network circumstances can strip the request header that tells the server to provide compressed assets. You can force gzip or deflate for all clients like this:
{
"citizen" : {
"compression" : {
"enabled" : true ,
"force" : " gzip "
}
}
}If you have request caching enabled, both the original (identity) and compressed (gzip and deflate) versions of the request will be cached, so your cache's memory utilization will increase.
citizen includes basic request payload parsing. When a user submits a form, the parsed form data is available in your controller via params.form . If you want to use a third-party package to parse the form data yourself, you can disable form parsing in the config and access the raw payload via request.payload .
// login controller
export const handler = ( params ) => {
// Set some defaults for the login view
params . form . username = ''
params . form . password = ''
params . form . remember = false
}
// Using a separate action in your controller for form submissions is probably a good idea
export const submit = async ( params ) => {
let authenticate = await app . models . user . authenticate ( {
username : params . form . username ,
password : params . form . password
} ) ,
cookies = { }
if ( authenticate . success ) {
if ( params . form . remember ) {
cookies . username : authenticate . username
}
return {
cookies : cookies ,
redirect : '/'
}
} else {
return {
local : {
message : 'Login failed.'
}
}
}
}If it's a multipart form containing a file, the form object passed to your controller will look something like this:
{
field1 : 'bar' ,
field2 : 'buzz' ,
fileField1 : {
filename : 'file.png' ,
contentType : 'image/png' ,
binary : < binary data >
}
} File contents are presented in binary format, so you'll need to use Buffer.from(fileField1.binary, 'binary') to create the actual file for storage.
You can pass global form settings via citizen.form in the config or at the controller action level via controller config (see below).
Use the maxPayloadSize config to limit form uploads. The following config sets the maxFieldsSize to 512k:
{
"citizen" : {
"forms" : {
"maxPayloadSize" : 500000 // 0.5MB
}
}
} The maxPayloadSize option includes text inputs and files in a multipart form in its calculations. citizen throws an error if form data exceeds this amount.
Certain events will occur throughout the life of your citizen application, or within each request. You can act on these events, execute functions, set directives, and pass the results to the next event or controller via the context argument. For example, you might set a cookie at the beginning of every new session, or check for cookies at the beginning of every request and redirect the user to a login page if they're not authenticated.
To take advantage of these events, include a directory called "hooks" in your app with any or all of following modules and exports:
app/
controllers/
hooks/
application.js // exports start() and error()
request.js // exports start() and end()
response.js // exports start() and end()
session.js // exports start() and end()
request.start() , request.end() , and response.start() are called before your controller is fired, so the output from those events is passed from each one to the next, and ultimately to your controller via the context argument. Exactly what actions they perform and what they output—content, citizen directives, custom directives—is up to you.
All files and exports are optional. citizen parses them at startup and only calls them if they exist. For example, you could have only a request.js module that exports start() .
Here's an example of a request module that checks for a username cookie at the beginning of every request and redirects the user to the login page if it doesn't exist. We also avoid a redirect loop by making sure the requested controller isn't the login controller:
// app/controllers/hooks/request.js
export const start = ( params ) => {
if ( ! params . cookie . username && params . route . controller !== 'login' ) {
return {
redirect = '/login'
}
}
} session.end is slightly different in terms of the arguments it receives, which consists only of a copy of the expired session (no longer active):
// app/controllers/hooks/session.js
export const end = ( expiredSession ) => {
// do something whenever a session ends
} By default, all controllers respond to requests from the host only. citizen supports cross-domain HTTP requests via access control headers.
To enable cross-domain access for individual controller actions, add a cors object with the necessary headers to your controller's exports:
export const config = {
// Each controller action can have its own CORS headers
handler : {
cors : {
'Access-Control-Allow-Origin' : 'http://www.foreignhost.com' ,
'Access-Control-Expose-Headers' : 'X-My-Custom-Header, X-Another-Custom-Header' ,
'Access-Control-Max-Age' : 600 ,
'Access-Control-Allow-Credentials' : 'true' ,
'Access-Control-Allow-Methods' : 'OPTIONS, PUT' ,
'Access-Control-Allow-Headers' : 'Content-Type' ,
'Vary' : 'Origin'
}
}
} Why not just use the HTTP Headers directive or set them manually with response.setHeader() ? When citizen receives a request from an origin other than the host, it checks for the cors export in your controller to provide a preflight response without you having to write your own logic within the controller action. You can of course check request.method and write logic to handle this manually if you prefer.
For more details on CORS, check out the W3C spec and the Mozilla Developer Network.
If you use citizen behind a proxy, such as NGINX or Apache, make sure you have a Forwarded header in your server configuration so citizen handles CORS requests correctly. Different protocols (HTTPS on your load balancer and HTTP in your citizen app) will cause CORS requests to fail without these headers.
Here's an example of how you might set this up in NGINX:
location / {
proxy_set_header Forwarded "for=$remote_addr;host=$host;proto=$scheme;";
proxy_pass http://127.0.0.1:3000;
}
citizen has a built-in application cache where you can store basically anything: strings, objects, buffers, static files, etc.
You can store any object in citizen's cache. The benefits of using cache over storing content in your own global app variables are built-in cache expiration and extension, as well as wrappers for reading, parsing, and storing file content.
citizen's default cache time is 15 minutes, which you can change in the config (see Configuration). Cached item lifespans are extended whenever they're accessed unless you pass resetOnAccess: false or change that setting in the config.
// Cache a string in the default app scope for 15 minutes (default). Keys
// must be unique within a given scope.
app . cache . set ( {
key : 'welcome-message' ,
value : 'Welcome to my site.'
} )
// Cache a string under a custom scope, which is used for retrieving or clearing
// multiple cache items at once. Keys must be unique within a given scope.
// Reserved scope names are "app", "routes", and "files".
app . cache . set ( {
key : 'welcome-message' ,
scope : 'site-messages' ,
value : 'Welcome to our site.'
} )
// Cache a string for the life of the application.
app . cache . set ( {
key : 'welcome-message' ,
value : 'Welcome to my site.' ,
lifespan : 'application'
} )
// Cache a file buffer using the file path as the key. This is a wrapper for
// fs.readFile and fs.readFileSync paired with citizen's cache function.
// Optionally, tell citizen to perform a synchronous file read operation and
// use an encoding different from the default (UTF-8).
app . cache . set ( {
file : '/path/to/articles.txt' ,
synchronous : true ,
encoding : 'CP-1252'
} )
// Cache a file with a custom key. Optionally, parse the JSON and store the
// parsed object in the cache instead of the raw buffer. Expire the cache
// after 10 minutes, regardless of whether the cache is accessed or not.
app . cache . set ( {
file : '/path/to/articles.json' ,
key : 'articles' ,
parseJSON : true ,
lifespan : 10 ,
resetOnAccess : false
} ) app , routes , and files are reserved scope names, so you can't use them for your own custom scopes.
This is a way to check for the existence of a given key or scope in the cache without resetting the cache timer on that item. Returns false if a match isn't found.
// Check for the existence of the specified key
let keyExists = app . cache . exists ( { key : 'welcome-message' } ) // keyExists is true
let keyExists = app . cache . exists ( { file : '/path/to/articles.txt' } ) // keyExists is true
let keyExists = app . cache . exists ( { file : 'articles' } ) // keyExists is true
let keyExists = app . cache . exists ( { key : 'foo' } ) // keyExists is false
// Check the specified scope for the specified key
let keyExists = app . cache . exists ( {
scope : 'site-messages' ,
key : 'welcome-message'
} )
// keyExists is true
// Check if the specified scope exists and contains items
let scopeExists = app . cache . exists ( {
scope : 'site-messages'
} )
// scopeExists is true
// Check if the route cache has any instances of the specified route
let controllerExists = app . cache . exists ( {
route : '/article'
} ) Retrieve an individual key or an entire scope. Returns false if the requested item doesn't exist. If resetOnAccess was true when the item was cached, using retrieve() will reset the cache clock and extend the life of the cached item. If a scope is retrieved, all items in that scope will have their cache timers reset.
Optionally, you can override the resetOnAccess attribute when retrieving a cache item by specifying it inline.
// Retrieve the specified key from the default (app) scope
let welcomeMessage = app . cache . get ( {
key : 'welcome-message'
} )
// Retrieve the specified key from the specified scope and reset its cache timer
// even if resetOnAccess was initially set to false when it was stored
let welcomeMessage = app . cache . get ( {
scope : 'site-messages' ,
key : 'welcome-message' ,
resetOnAccess : true
} )
// Retrieve all keys from the specified scope
let siteMessages = app . cache . get ( {
scope : 'site-messages'
} )
// Retrieve a cached file
let articles = app . cache . get ( {
file : '/path/to/articles.txt'
} )
// Retrieve a cached file with its custom key
let articles = app . cache . get ( {
file : 'articles'
} )Clear a cache object using a key or a scope.
// Store some cache items
app . cache . set ( {
key : 'welcome-message' ,
scope : 'site-messages' ,
value : 'Welcome to our site.'
} )
app . cache . set ( {
key : 'goodbye-message' ,
scope : 'site-messages' ,
value : 'Thanks for visiting!'
} )
app . cache . set ( {
file : '/path/to/articles.txt' ,
synchronous : true
} )
// Clear the welcome message from its custom scope cache
app . cache . clear ( { scope : 'site-messages' , key : 'welcome-message' } )
// Clear all messages from the cache using their custom scope
app . cache . clear ( { scope : 'site-messages' } )
// Clear the articles cache from the file scope
app . cache . clear ( { file : '/path/to/articles.txt' } ) cache.clear() can also be used to delete cached requests and controller actions.
app . cache . clear ( {
route : '/article/My-Article/page/2'
} )
// Clear the entire route scope
app . cache . clear ( { scope : 'routes' } )
// Clear the entire file scope
app . cache . clear ( { scope : 'files' } )
// Clear the entire cache
app . cache . clear ( ) citizen's log() function is exposed for use in your app via app.log() .
Makes it easy to log comments to either the console or a file (or both) in a way that's dependent on the mode of the framework.
When citizen is in production mode, log() does nothing by default. In development mode, log() will log whatever you pass to it. This means you can place it throughout your application's code and it will only write to the log in development mode. You can override this behavior globally with the log settings in your config file or inline with the console or file options when calling log() .
app . log ( {
// Optional. Valid settings are "status" (default) or "error".
type : 'status' ,
// Optional string. Applies a label to your log item.
label : 'Log output' ,
// The content of your log. If it's anything other than a string or
// number, log() will run util.inspect on it and dump the contents.
contents : someObject ,
// Optional. Enables console logs.
console : true ,
// Optional. Enables file logging.
file : false ,
// Optional. File name you'd like to write your log to.
file : 'my-log-file.log' ,
// Optional. Disables the timestamp that normally appears in front of the log
timestamp : false
} ) Log files appear in the directory you specify in config.citizen.directories.logs .
Warning: development mode is inherently insecure. Don't use it in a production environment.
If you set "mode": "development" in your config file, citizen dumps all major operations to the console.
You can also dump the request context to the view by setting development.debug.view in your config file to true , or use the ctzn_debug URL parameter on a per-request basis:
// config file: always dumps debug output in the view
{
"citizen" : {
"development" : {
"debug" : {
"view" : true
}
}
}
} By default, citizen dumps the pattern's complete context. You can specify the exact object to debug with the ctzn_inspect URL parameter:
// Dumps the server params object
http://www.cleverna.me/article/id/237/page/2/ctzn_debug/true/ctzn_inspect/params
// Dumps the user's session scope
http://www.cleverna.me/article/id/237/page/2/ctzn_debug/true/ctzn_inspect/params.session
The debug output traverses objects 4 levels deep by default. To display deeper output, use the development.debug.depth setting in your config file or append ctzn_debugDepth to the URL. Debug rendering will take longer the deeper you go.
// config file: debug 4 levels deep
{
"citizen" : {
"development" : {
"debug" : {
"depth" : 6
}
}
}
}
// URL
// http://www.cleverna.me/article/id/237/page/2/ctzn_debug/true/ctzn_debugDepth/4 In development mode, you must specify the ctzn_debug URL parameter to display debug output. Debug output is disabled in production mode.
The util directory within the citizen package has some helpful utilities.
Creates a complete skeleton of a citizen app with a functional index pattern and error templates.
$ node node_modules/citizen/util/scaffold skeletonResulting file structure:
app/
config/
citizen.json
controllers/
hooks/
application.js
request.js
response.js
session.js
routes/
index.js
models/
index.js
views/
error/
404.html
500.html
ENOENT.html
error.html
index.html
start.js
web/
Run node node_modules/citizen/util/scaffold skeleton -h for options.
Creates a complete citizen MVC pattern. The pattern command takes a pattern name and options:
$ node node_modules/citizen/util/scaffold pattern [options] [pattern] For example, node scaffold pattern article will create the following pattern:
app/
controllers/
routes/
article.js
models/
article.js
views/
article/
article.html
Use node node_modules/citizen/util/scaffold pattern -h to see all available options for customizing your patterns.
(The MIT License)
Copyright (c) 2014-2024 Jay Sylvester
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
Die Software wird "wie ist" ohne Garantie jeglicher Art, ausdrücklich oder stillschweigend bereitgestellt, einschließlich, aber nicht beschränkt auf die Gewährleistung der Handelsfähigkeit, die Eignung für einen bestimmten Zweck und die Nichtverletzung. In keinem Fall sind die Autoren oder Urheberrechtsinhaber für Ansprüche, Schäden oder andere Haftungen haftbar, sei es in einer Vertragsklage, unerbittlich oder auf andere Weise, die sich aus oder im Zusammenhang mit der Software oder anderen Geschäften in der Software ergeben.
By default, citizen dumps the pattern's complete context. You can specify the exact object to debug with the ctzn_inspect URL parameter:
// Dumps the server params object
http://www.cleverna.me/article/id/237/page/2/ctzn_debug/true/ctzn_inspect/params
// Dumps the user's session scope
http://www.cleverna.me/article/id/237/page/2/ctzn_debug/true/ctzn_inspect/params.session
The debug output traverses objects 4 levels deep by default. To display deeper output, use the development.debug.depth setting in your config file or append ctzn_debugDepth to the URL. Debug rendering will take longer the deeper you go.
// config file: debug 4 levels deep
{
"citizen" : {
"development" : {
"debug" : {
"depth" : 6
}
}
}
}
// URL
// http://www.cleverna.me/article/id/237/page/2/ctzn_debug/true/ctzn_debugDepth/4 In development mode, you must specify the ctzn_debug URL parameter to display debug output. Debug output is disabled in production mode.
The util directory within the citizen package has some helpful utilities.
Creates a complete skeleton of a citizen app with a functional index pattern and error templates.
$ node node_modules/citizen/util/scaffold skeletonResulting file structure:
app/
config/
citizen.json
controllers/
hooks/
application.js
request.js
response.js
session.js
routes/
index.js
models/
index.js
views/
error/
404.html
500.html
ENOENT.html
error.html
index.html
start.js
web/
Run node node_modules/citizen/util/scaffold skeleton -h for options.
Creates a complete citizen MVC pattern. The pattern command takes a pattern name and options:
$ node node_modules/citizen/util/scaffold pattern [options] [pattern] For example, node scaffold pattern article will create the following pattern:
app/
controllers/
routes/
article.js
models/
article.js
views/
article/
article.html
Use node node_modules/citizen/util/scaffold pattern -h to see all available options for customizing your patterns.
(The MIT License)
Copyright (c) 2014-2024 Jay Sylvester
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
Die Software wird "wie ist" ohne Garantie jeglicher Art, ausdrücklich oder stillschweigend bereitgestellt, einschließlich, aber nicht beschränkt auf die Gewährleistung der Handelsfähigkeit, die Eignung für einen bestimmten Zweck und die Nichtverletzung. In keinem Fall sind die Autoren oder Urheberrechtsinhaber für Ansprüche, Schäden oder andere Haftungen haftbar, sei es in einer Vertragsklage, unerbittlich oder auf andere Weise, die sich aus oder im Zusammenhang mit der Software oder anderen Geschäften in der Software ergeben.