ExpressJS Wie strukturiere ich eine Anwendung?


527

Ich verwende das ExpressJS-Webframework für NodeJS.

Benutzer von ExpressJS stellen ihre Umgebungen (Entwicklung, Produktion, Test ...), ihre Routen usw. auf die app.js. Ich denke, dass es kein schöner Weg ist, denn wenn Sie eine große Anwendung haben, ist app.js zu groß!

Ich möchte diese Verzeichnisstruktur haben:

| my-application
| -- app.js
| -- config/
     | -- environment.js
     | -- routes.js

Hier ist mein Code:

app.js.

var express = require('express');
var app = module.exports = express.createServer();

require('./config/environment.js')(app, express);
require('./config/routes.js')(app);

app.listen(3000);

config / environment.js

module.exports = function(app, express){
    app.configure(function() {
    app.use(express.logger());
    });

    app.configure('development', function() {
    app.use(express.errorHandler({
        dumpExceptions: true,
        showStack: true
    }));
    });

    app.configure('production', function() {
    app.use(express.errorHandler());
    });
};

config / route.js

module.exports = function(app) {
    app.get('/', function(req, res) {
    res.send('Hello world !');
    });
};

Mein Code funktioniert gut und ich finde die Struktur der Verzeichnisse wunderschön. Der Code musste jedoch angepasst werden und ich bin mir nicht sicher, ob er gut / schön ist.

Ist es besser, meine Verzeichnisstruktur zu verwenden und den Code anzupassen oder einfach eine Datei (app.js) zu verwenden?

Vielen Dank für Ihre Ratschläge!


Lauern die Leistungsprobleme auf diese Weise immer noch herum? Ich erinnere mich, dass ich irgendwo gelesen habe (vielleicht in der Express-Gruppe), dass man eine Menge Leistung verliert, wenn man alles so trennt. So etwas wie Ihre Anforderungen / Sek. Werden um einen spürbaren Betrag sinken, fast als wäre es ein Fehler.
AntelopeSalad

2
Es war von der Express Google-Gruppe. Hier ist der Link: groups.google.com/group/express-js/browse_thread/thread/…
AntelopeSalad

52
Nein, das ist sehr unwahr
tjholowaychuk

Antworten:


306

OK, es ist eine Weile her und dies ist eine beliebte Frage. Deshalb habe ich ein Gerüst-Github-Repository mit JavaScript-Code und einer langen README-Datei erstellt, in der erläutert wird, wie ich eine mittelgroße express.js-Anwendung strukturieren möchte.

focusaurus / express_code_structure ist das Repo mit dem neuesten Code dafür. Pull-Anfragen willkommen.

Hier ist eine Momentaufnahme der README-Datei, da der Stackoverflow keine Nur-Link-Antworten mag. Ich werde einige Updates vornehmen, da dies ein neues Projekt ist, das ich weiter aktualisieren werde, aber letztendlich wird das Github-Repo der aktuelle Ort für diese Informationen sein.


Express-Codestruktur

Dieses Projekt ist ein Beispiel für die Organisation einer mittelgroßen Webanwendung von express.js.

Aktuell, um mindestens v4.14 Dezember 2016 auszudrücken

Build-Status

js-standard-style

Wie groß ist Ihre Bewerbung?

Webanwendungen sind nicht alle gleich, und meiner Meinung nach gibt es keine einzige Codestruktur, die auf alle express.js-Anwendungen angewendet werden sollte.

Wenn Ihre Anwendung klein ist, benötigen Sie keine so tiefe Verzeichnisstruktur wie hier dargestellt. Halten Sie es einfach und stecken Sie eine Handvoll .jsDateien in das Stammverzeichnis Ihres Repositorys, und schon sind Sie fertig. Voilà.

Wenn Ihre Anwendung sehr umfangreich ist, müssen Sie sie irgendwann in verschiedene npm-Pakete aufteilen. Im Allgemeinen scheint der Ansatz von node.js viele kleine Pakete zu bevorzugen, zumindest für Bibliotheken, und Sie sollten Ihre Anwendung mithilfe mehrerer npm-Pakete aufbauen, da dies sinnvoll ist und den Overhead rechtfertigt. Wenn Ihre Anwendung wächst und ein Teil des Codes außerhalb Ihrer Anwendung eindeutig wiederverwendbar wird oder ein klares Subsystem ist, verschieben Sie ihn in das eigene Git-Repository und machen Sie ihn zu einem eigenständigen npm-Paket.

So im Mittelpunkt dieses Projektes ist es, eine tragfähige Struktur für eine mittelgroße Anwendung zu illustrieren.

Was ist Ihre Gesamtarchitektur?

Es gibt viele Ansätze zum Erstellen einer Webanwendung, z

  • Serverseitige MVC a la Ruby on Rails
  • Einseitiger Anwendungsstil a la MongoDB / Express / Angular / Node (MEAN)
  • Grundlegende Website mit einigen Formularen
  • Modelle / Operationen / Ansichten / Ereignisse Stil a la MVC ist tot, es ist Zeit, weiterzumachen
  • und viele andere sowohl aktuelle als auch historische

Jedes davon passt gut in eine andere Verzeichnisstruktur. In diesem Beispiel handelt es sich nur um ein Gerüst und nicht um eine voll funktionsfähige App. Ich gehe jedoch von den folgenden wichtigen Architekturpunkten aus:

  • Die Site enthält einige traditionelle statische Seiten / Vorlagen
  • Der "Anwendungs" -Teil der Site wird als Single Page Application-Stil entwickelt
  • Die Anwendung stellt dem Browser eine API im REST / JSON-Stil zur Verfügung
  • Die App modelliert eine einfache Geschäftsdomäne. In diesem Fall handelt es sich um eine Autohausanwendung

Und was ist mit Ruby on Rails?

Während des gesamten Projekts wird es ein Thema sein, dass viele der in Ruby on Rails und den von ihnen getroffenen Entscheidungen zum "Übereinkommen über die Konfiguration" enthaltenen Ideen, obwohl sie weithin akzeptiert und verwendet werden, nicht wirklich sehr hilfreich sind und manchmal das Gegenteil von dem sind, was dieses Repository darstellt empfiehlt.

Mein Hauptpunkt hier ist, dass der Organisation von Code grundlegende Prinzipien zugrunde liegen. Basierend auf diesen Prinzipien sind die Ruby on Rails-Konventionen (meistens) für die Ruby on Rails-Community sinnvoll. Nur gedankenlos diese Konventionen nachzuahmen, geht jedoch daneben. Sobald Sie die Grundprinzipien verstanden haben, sind ALLE Ihre Projekte gut organisiert und klar: Shell-Skripte, Spiele, mobile Apps, Unternehmensprojekte und sogar Ihr Home-Verzeichnis.

Für die Rails-Community möchten sie, dass ein einzelner Rails-Entwickler von App zu App zu App wechselt und jedes Mal damit vertraut und vertraut ist. Dies ist sehr sinnvoll, wenn Sie 37 Signale oder Pivotal Labs sind, und hat Vorteile. In der serverseitigen JavaScript-Welt ist das allgemeine Ethos einfach viel wilder im Westen, und wir haben damit kein wirkliches Problem. Das ist unsere Art. Wir sind daran gewöhnt. Selbst innerhalb von express.js ist es eine enge Verwandtschaft von Sinatra, nicht von Rails, und Konventionen von Rails zu übernehmen, hilft normalerweise nichts. Ich würde sogar Prinzipien über Konvention über Konfiguration sagen .

Grundprinzipien und Motivationen

  • Sei geistig überschaubar
    • Das Gehirn kann nur mit einer kleinen Anzahl verwandter Dinge gleichzeitig umgehen und darüber nachdenken. Deshalb verwenden wir Verzeichnisse. Es hilft uns, mit Komplexität umzugehen, indem wir uns auf kleine Portionen konzentrieren.
  • Seien Sie größengerecht
    • Erstellen Sie keine "Mansion Directories", in denen nur 1 Datei und 3 Verzeichnisse vorhanden sind. Sie können dies in den Ansible Best Practices sehen , die kleine Projekte beschämen, mehr als 10 Verzeichnisse für mehr als 10 Dateien zu erstellen, wenn 1 Verzeichnis mit 3 Dateien viel besser geeignet wäre. Sie fahren keinen Bus zur Arbeit (es sei denn, Sie sind Busfahrer, aber selbst dann fahren Sie einen Bus bei der Arbeit nicht). Erstellen Sie also keine Dateisystemstrukturen, die nicht durch die tatsächlichen Dateien in ihnen gerechtfertigt sind .
  • Seien Sie modular, aber pragmatisch
    • Die Knotengemeinschaft bevorzugt insgesamt kleine Module. Alles, was sauber von Ihrer App getrennt werden kann, sollte entweder für den internen Gebrauch in ein Modul extrahiert oder auf npm öffentlich veröffentlicht werden. Bei den hier verwendeten mittelgroßen Anwendungen kann der Overhead jedoch Ihren Workflow ohne angemessenen Wert langwierig machen. Wenn Sie also einen Code haben, der herausgerechnet wird, aber nicht ausreicht, um ein vollständig separates npm-Modul zu rechtfertigen, betrachten Sie ihn einfach als " Protomodul " mit der Erwartung, dass er extrahiert wird, wenn er einen Größenschwellenwert überschreitet.
    • Einige Leute wie @ hij1nx umfassen auch ein app/node_modulesVerzeichnis und haben package.jsonDateien in den Proto-Modul Verzeichnissen , dass der Übergang zu erleichtern und als Erinnerung dienen.
  • Seien Sie einfach, Code zu finden
    • Angesichts einer zu erstellenden Funktion oder eines zu behebenden Fehlers ist es unser Ziel, dass ein Entwickler keine Probleme hat, die beteiligten Quelldateien zu finden.
    • Namen sind aussagekräftig und genau
    • Crufty-Code wird vollständig entfernt, nicht in einer verwaisten Datei belassen oder nur auskommentiert
  • Seien Sie suchfreundlich
    • Der gesamte Quellcode von Erstanbietern befindet sich im appVerzeichnis, sodass Sie cddort find / grep / xargs / ag / ack / etc ausführen können und sich nicht von Übereinstimmungen mit Drittanbietern ablenken lassen
  • Verwenden Sie eine einfache und offensichtliche Benennung
    • npm scheint nun Paketnamen in Kleinbuchstaben zu erfordern. Ich finde das meistens schrecklich, aber ich muss der Herde folgen, daher sollten Dateinamen verwendet werden kebab-case, obwohl der Variablenname dafür in JavaScript sein muss, camelCaseweil -es in JavaScript ein Minuszeichen ist.
    • Der Variablenname entspricht dem Basisnamen des Modulpfads, wird jedoch mit kebab-casetransformiert incamelCase
  • Gruppieren nach Kopplung, nicht nach Funktion
    • Dies ist eine große Abweichung von der Ruby on Rails - Konvention von app/views, app/controllers, app/modelsusw.
    • Features werden einem vollständigen Stapel hinzugefügt, daher möchte ich mich auf einen vollständigen Stapel von Dateien konzentrieren, die für mein Feature relevant sind. Wenn ich dem Benutzermodell ein Telefonnummernfeld hinzufüge, interessiert mich kein anderer Controller als der Benutzerkontroller, und ich kümmere mich nicht um ein anderes Modell als das Benutzermodell.
    • Anstatt 6 Dateien zu bearbeiten, die sich jeweils in einem eigenen Verzeichnis befinden, und Tonnen anderer Dateien in diesen Verzeichnissen zu ignorieren, ist dieses Repository so organisiert, dass alle Dateien, die ich zum Erstellen eines Features benötige, zusammengefasst werden
    • Aufgrund der Art von MVC ist die Benutzeransicht mit der Benutzersteuerung gekoppelt, die mit dem Benutzermodell gekoppelt ist. Wenn ich also das Benutzermodell ändere, ändern sich diese drei Dateien häufig zusammen, aber der Deals Controller oder der Customer Controller sind entkoppelt und daher nicht beteiligt. Gleiches gilt normalerweise auch für Nicht-MVC-Designs.
    • Die Entkopplung im MVC- oder MOVE-Stil in Bezug auf den Code in welchem ​​Modul wird weiterhin empfohlen, aber das Verteilen der MVC-Dateien in Geschwisterverzeichnisse ist nur ärgerlich.
    • Somit hat jede meiner Routendateien den Teil der Routen, den sie besitzt. Eine routes.rbDatei im Rails-Stil ist praktisch, wenn Sie einen Überblick über alle Routen in der App wünschen. Wenn Sie jedoch Funktionen erstellen und Fehler beheben, kümmern Sie sich nur um die Routen, die für das zu ändernde Teil relevant sind.
  • Speichern Sie Tests neben dem Code
    • Dies ist nur eine Instanz von "Gruppe durch Kopplung", aber ich wollte es speziell ausrufen. Ich habe viele Projekte geschrieben, in denen die Tests unter einem parallelen Dateisystem namens "tests" ablaufen, und jetzt, da ich meine Tests im selben Verzeichnis wie den entsprechenden Code abgelegt habe, werde ich nie mehr zurückkehren. Dies ist modularer und in Texteditoren viel einfacher zu handhaben und verringert einen Großteil des "../../ .." - Pfadquatsches. Wenn Sie Zweifel haben, probieren Sie es bei einigen Projekten aus und entscheiden Sie selbst. Ich werde nichts weiter tun, um Sie davon zu überzeugen, dass es besser ist.
  • Reduzieren Sie die Querschnittskopplung mit Events
    • Es ist leicht zu denken, "OK, wenn ein neuer Deal erstellt wird, möchte ich eine E-Mail an alle Vertriebsmitarbeiter senden" und dann einfach den Code zum Senden dieser E-Mails in die Route einzugeben, die Deals erstellt.
    • Diese Kopplung verwandelt Ihre App jedoch schließlich in einen riesigen Schlammball.
    • Stattdessen sollte das DealModel nur ein "create" -Ereignis auslösen und sich nicht bewusst sein, was das System als Reaktion darauf sonst noch tun könnte.
    • Wenn Sie auf diese Weise codieren, wird es viel einfacher, den gesamten benutzerbezogenen Code einzufügen, app/usersda nicht überall ein Rattennest gekoppelter Geschäftslogik vorhanden ist, das die Reinheit der Benutzercodebasis verschmutzt.
  • Der Codefluss ist nachvollziehbar
    • Mach keine magischen Dinge. Laden Sie keine Dateien aus magischen Verzeichnissen im Dateisystem automatisch. Sei keine Schiene. Die App startet um app/server.js:1und Sie können alles sehen, was sie lädt und ausführt, indem Sie dem Code folgen.
    • Machen Sie keine DSLs für Ihre Routen. Machen Sie keine albernen Metaprogrammierungen, wenn dies nicht erforderlich ist.
    • Wenn Ihre App so groß ist , das zu tun magicRESTRouter.route(somecontroller, {except: 'POST'})ist ein großer Gewinn für Sie über 3 Grund app.get, app.put, app.del, Anrufe, sind Sie wahrscheinlich ein monolithisches App bauen , die zu groß ist , effektiv zu arbeiten. Lust auf GROSSE Gewinne, nicht darauf, 3 einfache Zeilen in 1 komplexe Zeile umzuwandeln.
  • Verwenden Sie Dateinamen in Kleinbuchstaben

    • Dieses Format vermeidet plattformübergreifende Probleme mit der Groß- und Kleinschreibung des Dateisystems
    • npm verbietet Großbuchstaben in neuen Paketnamen, und das funktioniert gut damit

      express.js Besonderheiten

  • Nicht benutzen app.configure. Es ist fast völlig nutzlos und du brauchst es einfach nicht. Es ist in vielen Kesselplatten wegen sinnloser Copypasta.

  • DIE BESTELLUNG VON MITTELWARE UND ROUTEN IN AUSDRÜCKLICHEN ANGELEGENHEITEN !!!
    • Fast jedes Routing-Problem, das ich beim Stackoverflow sehe, ist Express-Middleware außerhalb der Reihenfolge
    • Im Allgemeinen möchten Sie, dass Ihre Routen entkoppelt werden und sich nicht so sehr auf die Reihenfolge verlassen
    • Verwenden Sie es nicht app.usefür Ihre gesamte Anwendung, wenn Sie diese Middleware wirklich nur für zwei Routen benötigen (ich sehe Sie an body-parser).
    • Stellen Sie sicher, dass Sie genau diese Reihenfolge haben, wenn alles gesagt und getan ist:
      1. Jede überaus wichtige anwendungsweite Middleware
      2. Alle Ihre Routen und verschiedene Routen-Middlewares
      3. DANN Fehlerbehandlungsroutinen
  • Leider geht express.js, da es von Sinatra inspiriert ist, meistens davon aus, dass sich alle Ihre Routen befinden server.jsund es klar ist, wie sie bestellt werden. Für eine mittelgroße Anwendung ist es schön, Dinge in separate Routenmodule aufzuteilen, aber es besteht die Gefahr, dass Middleware nicht in Ordnung ist

Der App-Symlink-Trick

Es gibt viele Ansätze skizziert und ausführlich von der Gemeinschaft in dem großen Kern diskutiert bessere lokale require () Pfade für Node.js . Ich könnte mich bald dafür entscheiden, entweder "nur mit vielen ../../../ .. umzugehen" oder das requireFrom- Modul zu verwenden. Im Moment habe ich jedoch den unten beschriebenen Symlink-Trick verwendet.

Ein Weg, um projektinterne Anforderungen mit lästigen relativen Pfaden wie zu vermeiden, require("../../../config")besteht darin, den folgenden Trick zu verwenden:

  • Erstellen Sie unter node_modules einen Symlink für Ihre App
    • cd node_modules && ln -nsf ../app
  • fügen Sie einfach die node_modules / app Symlink selbst , nicht die gesamte node_modules Ordner, zu git
    • git add -f node_modules / app
    • Ja, Sie sollten immer noch "node_modules" in Ihrer .gitignoreDatei haben
    • Nein, Sie sollten "node_modules" nicht in Ihr Git-Repository einfügen. Einige Leute werden Ihnen dies empfehlen. Sie sind falsch.
  • Jetzt können Sie projektinterne Module mit diesem Präfix anfordern
    • var config = require("app/config");
    • var DealModel = require("app/deals/deal-model");;
  • Grundsätzlich erfordert dies projektinterne Arbeiten, die den Anforderungen für externe npm-Module sehr ähnlich sind.
  • Entschuldigung, Windows-Benutzer, Sie müssen sich an die relativen Pfade des übergeordneten Verzeichnisses halten.

Aufbau

Im Allgemeinen erwarten Codemodule und Klassen nur ein übergebenes grundlegendes JavaScript- optionsObjekt. Nur app/server.jsdas app/config.jsModul sollte geladen werden. Von dort aus können kleine optionsObjekte synthetisiert werden, um Subsysteme nach Bedarf zu konfigurieren. Die Kopplung jedes Subsystems an ein großes globales Konfigurationsmodul mit zusätzlichen Informationen ist jedoch eine schlechte Kopplung.

Versuchen Sie, die Erstellung von DB-Verbindungen zu zentralisieren und diese an Subsysteme zu übergeben, anstatt Verbindungsparameter zu übergeben und Subsysteme selbst ausgehende Verbindungen herstellen zu lassen.

NODE_ENV

Dies ist eine weitere verlockende, aber schreckliche Idee, die von Rails übernommen wurde. Es sollte genau 1 Stelle in Ihrer App sein, app/config.jsdie die NODE_ENVUmgebungsvariable betrachtet. Alles andere sollte eine explizite Option als Klassenkonstruktorargument oder Modulkonfigurationsparameter annehmen.

Wenn das E-Mail-Modul eine Option zum Zustellen von E-Mails hat (SMTP, Protokollieren bei stdout, Einfügen in die Warteschlange usw.), sollte es eine Option wie diese verwenden {deliver: 'stdout'}, diese sollte jedoch absolut nicht überprüft werden NODE_ENV.

Tests

Ich behalte meine Testdateien jetzt im selben Verzeichnis wie der entsprechende Code und verwende Namenskonventionen für Dateinamenerweiterungen, um Tests von Produktionscode zu unterscheiden.

  • foo.js hat den Code des Moduls "foo"
  • foo.tape.js hat die knotenbasierten Tests für foo und lebt im selben Verzeichnis
  • foo.btape.js kann für Tests verwendet werden, die in einer Browserumgebung ausgeführt werden müssen

Ich verwende Dateisystem-Globs und den find . -name '*.tape.js'Befehl, um bei Bedarf auf alle meine Tests zuzugreifen.

So organisieren Sie Code in jeder .jsModuldatei

In diesem Projekt geht es hauptsächlich darum, wohin Dateien und Verzeichnisse gehen, und ich möchte nicht viel mehr hinzufügen, aber ich möchte nur erwähnen, dass ich meinen Code in drei verschiedene Abschnitte gegliedert habe.

  1. Das Öffnen des Blocks von CommonJS erfordert Aufrufe von Statusabhängigkeiten
  2. Hauptcodeblock von reinem JavaScript. Keine CommonJS-Verschmutzung hier. Verweisen Sie nicht auf Exporte, Module oder Anforderungen.
  3. Schließen des CommonJS-Blocks zum Einrichten von Exporten

1
Was soll ich anstelle von bodyParser verwenden, wenn ich nur wenige Routen habe, die es verwenden?
Ilan Frumer

3
Ich habe hier gefunden, wonach ich gesucht habe: stackoverflow.com/questions/12418372/…
Ilan Frumer

1
@wlingke Unter gist.github.com/branneman/8048520 finden Sie eine ausführliche Beschreibung der verfügbaren Ansätze für dieses Problem.
Peter Lyons

@peterLyons Danke, dass du das geteilt hast. Nach dem Lesen denke ich, dass ich ein Startskript schreiben werde. Vielen Dank!
Wlingke

2
In Bezug auf den The App Symlink Trick gibt es dieses kleine Modul, das alle Probleme verschwinden lässt
Hayko Koryun

157

UPDATE (29.10.2013) : Bitte beachten Sie auch meine andere Antwort, die auf vielfachen Wunsch JavaScript anstelle von CoffeeScript enthält, sowie ein Github-Repo mit Boilerplate und eine ausführliche README-Datei mit meinen neuesten Empfehlungen zu diesem Thema.

Konfig

Was Sie tun, ist in Ordnung. Ich möchte meinen eigenen Konfigurations-Namespace in einer config.coffeeDatei der obersten Ebene mit einem verschachtelten Namespace wie diesem einrichten .

#Set the current environment to true in the env object
currentEnv = process.env.NODE_ENV or 'development'
exports.appName = "MyApp"
exports.env =
  production: false
  staging: false
  test: false
  development: false
exports.env[currentEnv] = true
exports.log =
  path: __dirname + "/var/log/app_#{currentEnv}.log"
exports.server =
  port: 9600
  #In staging and production, listen loopback. nginx listens on the network.
  ip: '127.0.0.1'
if currentEnv not in ['production', 'staging']
  exports.enableTests = true
  #Listen on all IPs in dev/test (for testing from other machines)
  exports.server.ip = '0.0.0.0'
exports.db =
  URL: "mongodb://localhost:27017/#{exports.appName.toLowerCase()}_#{currentEnv}"

Dies ist für die Bearbeitung von Systemadministratoren geeignet. Wenn ich dann etwas brauche, wie die DB-Verbindungsinformationen, ist es

require('./config').db.URL

Routen / Controller

Ich lasse meine Routen gerne bei meinen Controllern und organisiere sie in einem app/controllersUnterverzeichnis. Dann kann ich sie laden und sie die Routen hinzufügen lassen, die sie benötigen.

In meiner app/server.coffeeCoffeescript-Datei mache ich:

[
  'api'
  'authorization'
  'authentication'
  'domains'
  'users'
  'stylesheets'
  'javascripts'
  'tests'
  'sales'
].map (controllerName) ->
  controller = require './controllers/' + controllerName
  controller.setup app

Also habe ich Dateien wie:

app/controllers/api.coffee
app/controllers/authorization.coffee
app/controllers/authentication.coffee
app/controllers/domains.coffee

Und zum Beispiel habe ich in meinem Domänencontroller eine solche setupFunktion.

exports.setup = (app) ->
  controller = new exports.DomainController
  route = '/domains'
  app.post route, controller.create
  app.put route, api.needId
  app.delete route, api.needId
  route = '/domains/:id'
  app.put route, controller.loadDomain, controller.update
  app.del route, controller.loadDomain, exports.delete
  app.get route, controller.loadDomain, (req, res) ->
    res.sendJSON req.domain, status.OK

Ansichten

Das Einfügen von Ansichten app/viewswird zum üblichen Ort. Ich lege es so aus.

app/views/layout.jade
app/views/about.jade
app/views/user/EditUser.jade
app/views/domain/EditDomain.jade

Statische Dateien

Gehen Sie in ein publicUnterverzeichnis.

Github / Semver / NPM

Legen Sie eine README.md-Markdown-Datei in Ihrem Git-Repo-Stammverzeichnis für Github ab.

Fügen Sie eine package.json-Datei mit einer semantischen Versionsnummer in Ihr Git-Repo-Stammverzeichnis für NPM ein.


1
Hey Peter! Ich mag diesen Ansatz wirklich, den Sie anstreben. Ich arbeite daran, an einem Express-Projekt aufzubauen, und ich möchte die Dinge wirklich richtig machen, als sie nur zu hacken und herumzulegen. Wäre großartig, wenn Sie ein Beispiel-Repo auf Github und / oder einen Blog-Beitrag darauf hätten.
suVasH .....

4
Dieses Repo hat eine Reihe von Mustern, die Sie als Referenz verwenden können: github.com/focusaurus/peterlyons.com
Peter Lyons

75
Kaffee-Skript machen dies schwer zu lesen: / Gibt es eine Chance, eine Vanille-JS-Bearbeitung zu bekommen? Danke
toasted_flakes

1
Danke für diese Antwort. Ich versuche nur, mich darum zu kümmern. Wie greifen Sie auf die anderen Controller in einem anderen zu (z. B. in der Setup-Funktion wie obenapp.put route, api.needId
chmanie

@PeterLyons: Hey Mann, ich habe Ihren Quellcode gesehen, aber keine Ahnung, wie der Build-Modus ausgeführt wird. Ich habe Godie binDatei bereits installiert und in die Struktur aufgenommen. Wie führen Sie diese goDatei aus bin?
user2002495

51

Das Folgende ist die wörtliche Antwort von Peter Lyons, die von Coffeescript auf Vanilla JS übertragen wurde, wie von mehreren anderen angefordert. Peters Antwort ist sehr fähig, und jeder, der über meine Antwort abstimmt, sollte auch über seine abstimmen.


Konfig

Was Sie tun, ist in Ordnung. Ich möchte meinen eigenen Konfigurations-Namespace in einer config.jsDatei der obersten Ebene mit einem verschachtelten Namespace wie diesem einrichten .

// Set the current environment to true in the env object
var currentEnv = process.env.NODE_ENV || 'development';
exports.appName = "MyApp";
exports.env = {
  production: false,
  staging: false,
  test: false,
  development: false
};  
exports.env[currentEnv] = true;
exports.log = {
  path: __dirname + "/var/log/app_#{currentEnv}.log"
};  
exports.server = {
  port: 9600,
  // In staging and production, listen loopback. nginx listens on the network.
  ip: '127.0.0.1'
};  
if (currentEnv != 'production' && currentEnv != 'staging') {
  exports.enableTests = true;
  // Listen on all IPs in dev/test (for testing from other machines)
  exports.server.ip = '0.0.0.0';
};
exports.db {
  URL: "mongodb://localhost:27017/#{exports.appName.toLowerCase()}_#{currentEnv}"
};

Dies ist für die Bearbeitung von Systemadministratoren geeignet. Wenn ich dann etwas brauche, wie die DB-Verbindungsinformationen, ist es

require('./config').db.URL

Routen / Controller

Ich lasse meine Routen gerne bei meinen Controllern und organisiere sie in einem app/controllersUnterverzeichnis. Dann kann ich sie laden und sie die Routen hinzufügen lassen, die sie benötigen.

In meiner app/server.jsJavascript-Datei mache ich:

[
  'api',
  'authorization',
  'authentication',
  'domains',
  'users',
  'stylesheets',
  'javascripts',
  'tests',
  'sales'
].map(function(controllerName){
  var controller = require('./controllers/' + controllerName);
  controller.setup(app);
});

Also habe ich Dateien wie:

app/controllers/api.js
app/controllers/authorization.js
app/controllers/authentication.js
app/controllers/domains.js

Und zum Beispiel habe ich in meinem Domänencontroller eine solche setupFunktion.

exports.setup = function(app) {
  var controller = new exports.DomainController();
  var route = '/domains';
  app.post(route, controller.create);
  app.put(route, api.needId);
  app.delete(route, api.needId);
  route = '/domains/:id';
  app.put(route, controller.loadDomain, controller.update);
  app.del(route, controller.loadDomain, function(req, res){
    res.sendJSON(req.domain, status.OK);
  });
}

Ansichten

Das Einfügen von Ansichten app/viewswird zum üblichen Ort. Ich lege es so aus.

app/views/layout.jade
app/views/about.jade
app/views/user/EditUser.jade
app/views/domain/EditDomain.jade

Statische Dateien

Gehen Sie in ein publicUnterverzeichnis.

Github / Semver / NPM

Legen Sie eine README.md-Markdown-Datei in Ihrem Git-Repo-Stammverzeichnis für Github ab.

Fügen Sie eine package.json-Datei mit einer semantischen Versionsnummer in Ihr Git-Repo-Stammverzeichnis für NPM ein.


43

Meine Frage wurde im April 2011 gestellt, sie ist ziemlich alt. Während dieser Zeit konnte ich meine Erfahrung mit Express.js und die Architektur einer mit dieser Bibliothek geschriebenen Anwendung verbessern. Also teile ich hier meine Erfahrungen.

Hier ist meine Verzeichnisstruktur:

├── app.js   // main entry
├── config   // The configuration of my applications (logger, global config, ...)
├── models   // The model data (e.g. Mongoose model)
├── public   // The public directory (client-side code)
├── routes   // The route definitions and implementations
├── services // The standalone services (Database service, Email service, ...)
└── views    // The view rendered by the server to the client (e.g. Jade, EJS, ...)

App.js.

Das Ziel der app.jsDatei ist es, die expressjs-Anwendung zu booten. Es lädt das Konfigurationsmodul, das Logger-Modul, wartet auf die Datenbankverbindung, ... und führt den Express-Server aus.

'use strict';
require('./config');
var database = require('./services/database');
var express = require('express');
var app = express();
module.exports = app;

function main() {
  var http = require('http');

  // Configure the application.
  app.configure(function () {
    // ... ... ...
  });
  app.configure('production', function () {
    // ... ... ...
  });
  app.configure('development', function () {
    // ... ... ...
  });

  var server = http.createServer(app);

  // Load all routes.
  require('./routes')(app);

  // Listen on http port.
  server.listen(3000);
}

database.connect(function (err) {
  if (err) { 
    // ...
  }
  main();
});

Routen /

Das Routenverzeichnis enthält eine index.jsDatei. Ziel ist es, eine Art Magie einzuführen, um alle anderen Dateien in das routes/Verzeichnis zu laden . Hier ist die Implementierung:

/**
 * This module loads dynamically all routes modules located in the routes/
 * directory.
 */
'use strict';
var fs = require('fs');
var path = require('path');

module.exports = function (app) {
  fs.readdirSync('./routes').forEach(function (file) {
    // Avoid to read this current file.
    if (file === path.basename(__filename)) { return; }

    // Load the route file.
    require('./' + file)(app);
  });
};

Mit diesem Modul ist das Erstellen einer neuen Routendefinition und -implementierung wirklich einfach. Zum Beispiel hello.js:

function hello(req, res) {
  res.send('Hello world');
}

module.exports = function (app) {
  app.get('/api/hello_world', hello);
};

Jedes Routenmodul ist eigenständig .


Verwenden Sie einen Generator, um diese Struktur zu erstellen?
Ashish


17

Ich denke, es ist eine großartige Möglichkeit, dies zu tun. Nicht auf Express beschränkt, aber ich habe eine ganze Reihe von node.js-Projekten auf github gesehen, die dasselbe tun. Sie nehmen die Konfigurationsparameter heraus + kleinere Module (in einigen Fällen jeder URI) werden in separaten Dateien berücksichtigt.

Ich würde empfehlen, Express-spezifische Projekte auf Github durchzugehen, um sich ein Bild zu machen. IMO ist die Art und Weise, wie Sie es tun, richtig.


16

Es ist jetzt Ende 2015 und nachdem ich meine Struktur für 3 Jahre und in kleinen und großen Projekten entwickelt habe. Fazit?

Führen Sie keine große MVC durch, sondern trennen Sie sie in Module

Damit...

Warum?

  • Normalerweise arbeitet man an einem Modul (zB Produkte), das Sie unabhängig voneinander ändern können.

  • Sie können Module wiederverwenden

  • Sie können es separat testen

  • Sie können es separat ersetzen

  • Sie haben klare (stabile) Schnittstellen

    - Spätestens wenn mehrere Entwickler arbeiten, hilft die Modultrennung

Das Nodebootstrap- Projekt hat einen ähnlichen Ansatz wie meine endgültige Struktur. ( Github )

Wie sieht diese Struktur aus?

  1. Kleine, gekapselte Module mit jeweils separatem MVC

  2. Jedes Modul hat eine package.json

  3. Testen als Teil der Struktur (in jedem Modul)

  4. Globale Konfiguration , Bibliotheken und Dienste

  5. Integrierter Docker, Cluster, für immer

Ordnerübersicht (Module siehe lib-Ordner):

Nodebootstrap-Struktur


3
Es wäre hilfreich, wenn Sie das Ordnerübersichtsbild mit den einzelnen erweiterten Modulen aktualisieren könnten, als Beispiel dafür, wie Sie diese ebenfalls strukturieren würden.
youngrrrr

8

Ich gebe MVC-Stil Ordnerstruktur finden Sie unten.

Wir haben für unsere großen und mittleren Webanwendungen die folgende Ordnerstruktur verwendet.

 myapp   
|
|
|____app
|      |____controllers
|      |    |____home.js
|      |
|      |____models
|      |     |___home.js
|      |
|      |____views
|           |___404.ejs
|           |___error.ejs
|           |___index.ejs
|           |___login.ejs
|           |___signup.ejs
|   
|
|_____config
|     |___auth.js
|     |___constants.js
|     |___database.js
|     |___passport.js
|     |___routes.js
|
|
|____lib
|    |___email.js
|
|____node_modules
|
|
|____public.js
|    |____css
|    |    |__style.css
|    |    
|    |____js
|    |    |__script.js
|    |
|    |____img
|    |    |__img.jpg
|    |
|    |
|    |____uploads
|         |__img.jpg
|      
|   
|
|_____app.js
|
|
|
|_____package.json

Ich habe ein npm-Modul für die Generierung von Express-MVC-Ordner-Strukturierern erstellt.

Den folgenden https://www.npmjs.com/package/express-mvc-generator finden Sie unten

Nur einfache Schritte zum Generieren und Verwenden dieser Module.

i) Modul installieren npm install express-mvc-generator -g

ii) Optionen prüfen express -h

iii) Generieren Sie eine Express-MVC-Struktur express myapp

iv) Installieren Abhängigkeiten: npm install:

v) Öffnen Sie Ihre config / database.js. Bitte konfigurieren Sie Ihre Mongo-Datenbank.

vi) Führen Sie die Anwendung aus node appodernodemon app

vii) Überprüfen Sie die URL http: // localhost: 8042 / signup ODER http: // yourip: 8042 / signup


7

Seit der letzten Antwort auf diese Frage ist eine Weile vergangen, und Express hat kürzlich auch Version 4 veröffentlicht, die einige nützliche Dinge für die Organisation Ihrer App-Struktur enthält.

Im Folgenden finden Sie einen langen, aktuellen Blog-Beitrag über bewährte Methoden zur Strukturierung Ihrer Express-App. http://www.terlici.com/2014/08/25/best-practices-express-structure.html

Es gibt auch ein GitHub-Repository, das die Hinweise im Artikel anwendet. Es ist immer auf dem neuesten Stand der Express-Version.
https://github.com/terlici/base-express


7

Ich denke nicht, dass es ein guter Ansatz ist, Routen zur Konfiguration hinzuzufügen. Eine bessere Struktur könnte ungefähr so ​​aussehen:

application/
| - app.js
| - config.js
| - public/ (assets - js, css, images)
| - views/ (all your views files)
| - libraries/ (you can also call it modules/ or routes/)
    | - users.js
    | - products.js
    | - etc...

So enthalten products.js und users.js alle Ihre Routen und die gesamte Logik.


6

Nun, ich habe meine Routen als JSON-Datei abgelegt, die ich am Anfang gelesen habe, und in einer for-Schleife in app.js die Routen eingerichtet. Die route.json enthält die Ansicht, die aufgerufen werden soll, und den Schlüssel für die Werte, die an die Route gesendet werden.
Dies funktioniert in vielen einfachen Fällen, aber ich musste einige Routen für Sonderfälle manuell erstellen.


6

Ich habe genau zu diesem Thema einen Beitrag geschrieben. Grundsätzlich wird ein verwendet routeRegistrar, das Dateien in dem Ordner durchläuft, der /controllersseine Funktion aufruft init. Die Funktion verwendet initdie Express- appVariable als Parameter, damit Sie Ihre Routen nach Ihren Wünschen registrieren können.

var fs = require("fs");
var express = require("express");
var app = express();

var controllersFolderPath = __dirname + "/controllers/";
fs.readdirSync(controllersFolderPath).forEach(function(controllerName){
    if(controllerName.indexOf("Controller.js") !== -1){
        var controller = require(controllersFolderPath + controllerName);
        controller.init(app);
    }
});

app.listen(3000);


4

1) Ihr Express-Projektdateisystem könnte wie folgt aussehen:

/ ...
/lib
/node_modules
/public
/views
      app.js
      config.json
      package.json

app.js - Ihr globaler App-Container

2) Modulhauptdatei (lib / mymodule / index.js):

var express = require('express');    
var app = module.exports = express();
// and load module dependencies ...  

// this place to set module settings
app.set('view engine', 'jade');
app.set('views', __dirname + '/views');

// then do module staff    
app.get('/mymodule/route/',function(req,res){ res.send('module works!') });

3) Verbinden Sie das Modul in der Haupt-App.js

...
var mymodule = require('mymodule');
app.use(mymodule);

4) Beispiellogik

lib/login
lib/db
lib/config
lib/users
lib/verify
lib/
   /api/ 
   ...
lib/
   /admin/
      /users/
      /settings/
      /groups/
...
  • Am besten zum Testen
  • Am besten für die Skalierung
  • Die Trennung hängt vom Modul ab
  • Gruppieren der Route nach Funktionen (oder Modulen)

tj sagt / zeigt auf Vimeo interessante Idee, wie man Express-Anwendungen modularisiert - Modulare Webanwendungen mit Node.js und Express . Kraftvoll und einfach.


4

http://locomotivejs.org/ bietet eine Möglichkeit, eine mit Node.js und Express erstellte App zu strukturieren.

Von der Website:

"Locomotive ist ein Webframework für Node.js. Locomotive unterstützt MVC-Muster, RESTful-Routen und Konventionen über die Konfiguration und lässt sich nahtlos in jede Datenbank- und Vorlagen-Engine integrieren. Locomotive baut auf Express auf und bewahrt die Leistung und Einfachheit, die Sie erwarten vom Knoten. "


3

Ich habe kürzlich Module als unabhängige Mini-Apps angenommen.

|-- src
  |--module1
  |--module2
     |--www
       |--img
       |--js
       |--css
     |--#.js
     |--index.ejs
  |--module3
  |--www
     |--bower_components
     |--img
     |--js
     |--css
  |--#.js
  |--header.ejs
  |--index.ejs
  |--footer.ejs

Für jedes Modul-Routing (# .js) stehen jetzt Ansichten (* .ejs), js, css und Assets nebeneinander. Das Submodul-Routing wird in der übergeordneten # .js mit zwei zusätzlichen Zeilen eingerichtet

router.use('/module2', opt_middleware_check, require('./module2/#'));
router.use(express.static(path.join(__dirname, 'www')));

Auf diese Weise sind sogar Submodule möglich.

Vergessen Sie nicht, die Ansicht auf das Verzeichnis src zu setzen

app.set('views', path.join(__dirname, 'src'));

Jeder Link zu Github mit einer solchen Struktur, der interessiert ist, wie Routen, Ansichten und Modelle geladen werden
Muhammad Umer

Ich denke, alles ist erklärt. Routen sind nur klassische Expressrouten. Ansichten müssen mit dem Präfix Modulnamen geladen werden, Modelle müssen unter Bezugnahme auf den relativen Pfad geladen werden.
Zevero

In meiner letzten Zeile habe ich die Ansicht auf das Verzeichnis src gesetzt. Von nun an sind alle Ansichten relativ zum src-Verzeichnis zugänglich. Nichts Besonderes.
Zevero

1

So sieht der Großteil meiner Express-Projektverzeichnisstruktur aus.

Normalerweise express dirnameinitialisiere ich das Projekt, vergebe meine Faulheit, aber es ist sehr flexibel und erweiterbar. PS - das müssen Sie bekommen express-generator(für diejenigen, die danach suchen sudo npm install -g express-generator, sudo, weil Sie es global installieren)

|-- bin
    |-- www //what we start with "forever"
|-- bower_components
|-- models
    |-- database.js
    |-- model1.js //not this exact name ofcourse.
    |-- .
|-- node_modules
|-- public
    |-- images
    |-- javascripts
        |-- controllers
        |-- directives
        |-- services
        |-- app.js
        |-- init.js //contains config and used for initializing everything, I work with angular a lot.
    |-- stylesheets
|-- routes
    |-- some
    |-- hierarchy
    .
    .
|-- views
    |-- partials
    |-- content
|-- .env
|-- .env.template
|-- app.js
|-- README.md

Sie müssen sich fragen, warum .env-Dateien? Weil sie funktionieren! Ich benutze dotenvModul in meinen Projekten (viel in letzter Zeit) und es funktioniert! Fügen Sie diese 2 Anweisungen in app.jsoder einwww

var dotenv = require('dotenv');
dotenv.config({path: path.join(__dirname + "/.env")});

Und eine weitere Zeile, die schnell eingestellt werden muss /bower_components, um statische Inhalte unter der Ressource bereitzustellen/ext

app.use('/ext', express.static(path.join(__dirname, 'bower_components')));

Es kann wahrscheinlich für Leute geeignet sein, die Express und Angular zusammen verwenden möchten, oder javascriptsnatürlich nur ohne diese Hierarchie.


1

Meine Struktur drückt 4. https://github.com/odirleiborgert/borgert-express-boilerplate aus

Pakete

View engine: twig
Security: helmet
Flash: express-flash
Session: express-session
Encrypt: bcryptjs
Modules: express-load
Database: MongoDB
    ORM: Mongoose
    Mongoose Paginate
    Mongoose Validator
Logs: winston + winston-daily-rotate-file
Nodemon
CSS: stylus
Eslint + Husky

Struktur

|-- app
    |-- controllers
    |-- helpers
    |-- middlewares
    |-- models
    |-- routes
    |-- services
|-- bin
|-- logs
|-- node_modules
|-- public
    |-- components
    |-- images
    |-- javascripts
    |-- stylesheets
|-- views
|-- .env
|-- .env-example
|-- app.js
|-- README.md

0

Eine einfache Möglichkeit, Ihre Express-App zu strukturieren:

  • In main index.js sollte die folgende Reihenfolge beibehalten werden.

    Alle app.set sollten zuerst sein.

    Alle app.use sollten an zweiter Stelle stehen.

    gefolgt von anderen APIs mit ihren Funktionen oder Route-Continue in anderen Dateien

    Beispiel

    app.use ("/ password", passwordApi);

    app.use ("/ user", userApi);

    app.post ("/ token", passport.createToken);

    app.post ("/ logout", passport.logout)


0

Bester Weg zur MVC-Struktur für ExpressJs-Projekt mit Lenker und Passportjs

- app
      -config 
        -passport-setup.js
      -controllers
      -middleware
      -models
      -routes
      -service
    -bin
      -www
      -configuration.js
      -passport.js
    -node_modules
    -views
     -handlebars page
    -env
    -.gitignore
    -package.json
    -package-lock.json

@ Sandro-Munda bitte überprüfen
Manishkumar Bhavnani
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.