ExpressJS Jak zbudować aplikację?

527

Używam frameworka sieci ExpressJS dla NodeJS.

Ludzie korzystający z ExpressJS umieszczają swoje środowiska (programowanie, produkcja, testy ...), trasy itp app.js. W Internecie . Myślę, że nie jest to piękny sposób, ponieważ gdy masz dużą aplikację, plik app.js jest za duży!

Chciałbym mieć tę strukturę katalogów:

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

Oto mój kod:

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 / trasy.js

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

Mój kod działa dobrze i myślę, że struktura katalogów jest piękna. Jednak kod musiał zostać dostosowany i nie jestem pewien, czy jest dobry / piękny.

Czy lepiej jest użyć mojej struktury katalogów i dostosować kod, czy po prostu użyć jednego pliku (app.js)?

Dziękuję za twoje porady!

Sandro Munda
źródło
Czy wciąż czają się problemy z wydajnością robienia tego w ten sposób? Pamiętam, że gdzieś czytałem (może w grupie ekspresowej), że kiedy oddzielasz wszystko w ten sposób, tracisz mnóstwo wydajności. Coś w rodzaju twoich wymagań / sekund spadnie o zauważalną ilość, prawie jak w przypadku błędu.
AntelopeSalad
2
Pochodzi z grupy Express Google. Oto link: groups.google.com/group/express-js/browse_thread/thread/…
AntelopeSalad
52
nie, to jest bardzo nieprawdziwe
tjholowaychuk

Odpowiedzi:

306

OK, minęło trochę czasu i jest to popularne pytanie, więc stworzyłem repozytorium github rusztowania z kodem JavaScript i długim README o tym, jak lubię tworzyć struktury średniej aplikacji express.js.

focusaurus / express_code_structure to repozytorium z najnowszym kodem do tego. Witamy w zaproszeniach do ściągania.

Oto migawka README, ponieważ przepełnienie stosu nie lubi odpowiedzi typu „tylko łącze”. Zrobię kilka aktualizacji, ponieważ jest to nowy projekt, który będę nadal aktualizować, ale ostatecznie repozytorium github będzie aktualnym miejscem dla tych informacji.


Struktura ekspresowego kodu

Ten projekt jest przykładem organizacji średniej aplikacji sieci web express.js.

Aktualne co najmniej ekspresowe v4.14 grudnia 2016 r

Stan kompilacji

js-standard-style

Jak duża jest twoja aplikacja?

Aplikacje internetowe nie są takie same i moim zdaniem nie ma jednej struktury kodu, którą należy zastosować do wszystkich aplikacji express.js.

Jeśli twoja aplikacja jest niewielka, nie potrzebujesz tak głębokiej struktury katalogów, jak pokazano tutaj. Po prostu zachowaj prostotę i wrzuć garść .jsplików do katalogu głównego repozytorium i gotowe. Voilà.

Jeśli twoja aplikacja jest ogromna, w pewnym momencie musisz podzielić ją na odrębne pakiety npm. Zasadniczo podejście node.js wydaje się faworyzować wiele małych pakietów, przynajmniej dla bibliotek, i powinieneś zbudować swoją aplikację, używając kilku pakietów npm, ponieważ zaczyna to mieć sens i uzasadniać narzut. Tak więc, gdy Twoja aplikacja rośnie, a część kodu staje się jednoznacznie wielokrotnego użytku poza aplikacją lub stanowi przejrzysty podsystem, przenieś ją do własnego repozytorium git i przekształć w samodzielny pakiet npm.

Dlatego celem tego projektu jest zilustrowanie praktycznej struktury dla średnich aplikacji.

Jaka jest twoja ogólna architektura

Istnieje wiele podejść do tworzenia aplikacji internetowych, takich jak

  • Po stronie serwera MVC a la Ruby on Rails
  • Styl aplikacji jednostronicowej a la MongoDB / Express / Angular / Node (MEAN)
  • Podstawowa strona internetowa z niektórymi formularzami
  • Modele / Operacje / Widoki / Wydarzenia a la MVC nie żyje, czas na MOVE
  • i wiele innych, zarówno obecnych, jak i historycznych

Każdy z nich ładnie pasuje do innej struktury katalogów. Na potrzeby tego przykładu jest to tylko rusztowanie, a nie w pełni działająca aplikacja, ale zakładam następujące kluczowe punkty architektury:

  • Witryna ma kilka tradycyjnych statycznych stron / szablonów
  • Część witryny dotycząca aplikacji została opracowana jako styl aplikacji pojedynczej strony
  • Aplikacja wyświetla interfejs API stylu REST / JSON w przeglądarce
  • Aplikacja modeluje prostą domenę biznesową, w tym przypadku jest to aplikacja dealera samochodowego

A co z Ruby on Rails?

Tematem całego projektu będzie to, że wiele pomysłów zawartych w Ruby on Rails i przyjęte przez nich decyzje „Konwencja w sprawie konfiguracji”, choć powszechnie akceptowane i stosowane, nie są w rzeczywistości bardzo pomocne i czasami są przeciwieństwem tego, co to repozytorium poleca.

Chodzi mi przede wszystkim o to, że istnieją podstawowe zasady organizowania kodu i na podstawie tych zasad konwencje Ruby on Rails mają sens (głównie) dla społeczności Ruby on Rails. Jednak bezmyślne aportowanie tych konwencji mija się z celem. Po zapoznaniu się z podstawowymi zasadami WSZYSTKIE projekty będą dobrze zorganizowane i przejrzyste: skrypty powłoki, gry, aplikacje mobilne, projekty korporacyjne, a nawet katalog domowy.

Społeczność Railsów chce mieć możliwość przełączenia jednego dewelopera Railsów z aplikacji na aplikację i za każdym razem dobrze się z tym czuje. Ma to sens, jeśli masz 37 sygnałów lub Pivotal Labs i ma swoje zalety. W świecie JavaScript po stronie serwera ogólny etos jest o wiele bardziej dziki i zachodni, a tak naprawdę nie mamy z tym problemu. Tak się toczymy. Jesteśmy do tego przyzwyczajeni. Nawet w Express.js, jest to bliski krew Sinatry, a nie Railsów, a przyjmowanie konwencji z Railsów zwykle nic nie pomaga. Powiedziałbym nawet, że zasady są ważniejsze niż konwencja .

Podstawowe zasady i motywacje

  • Bądź zarządzalny psychicznie
    • Mózg może poradzić sobie i pomyśleć o niewielkiej liczbie powiązanych rzeczy na raz. Dlatego korzystamy z katalogów. Pomaga nam radzić sobie ze złożonością, koncentrując się na małych porcjach.
  • Dopasuj rozmiar
    • Nie twórz „katalogów rezydencji”, w których jest tylko 1 plik sam, 3 katalogi w dół. Możesz to zobaczyć w Ansible Best Practices, która zawstydza małe projekty do tworzenia ponad 10 katalogów do przechowywania ponad 10 plików, gdy 1 katalog z 3 plikami byłby o wiele bardziej odpowiedni. Nie jeździsz autobusem do pracy (chyba że jesteś kierowcą autobusu, ale nawet wtedy, gdy jedziesz autobusem AT nie działa TO), więc nie twórz struktur systemu plików, które nie są uzasadnione rzeczywistymi plikami w nich zawartymi .
  • Bądź modułowy, ale pragmatyczny
    • Społeczność węzłów preferuje małe moduły. Wszystko, co można całkowicie oddzielić od aplikacji, należy wyodrębnić do modułu do użytku wewnętrznego lub opublikować publicznie na npm. Jednak w przypadku średnich aplikacji, które są tutaj objęte, narzut ten może dodać nudę do przepływu pracy bez współmiernej wartości. Tak więc na czas, gdy masz jakiś wyodrębniony kod, ale niewystarczający, aby uzasadnić całkowicie oddzielny moduł npm, po prostu uważaj go za „ proto-moduł ” z oczekiwaniem, że gdy przekroczy on pewien próg wielkości, zostanie wyodrębniony.
    • Niektóre osoby, takie jak @ hij1nx, nawet zawierają app/node_moduleskatalog i mają package.jsonpliki w katalogach modułów, aby ułatwić to przejście i działają jako przypomnienie.
  • Łatwo zlokalizować kod
    • Biorąc pod uwagę funkcję do zbudowania lub błąd do naprawienia, naszym celem jest, aby programista nie miał trudności z lokalizacją zaangażowanych plików źródłowych.
    • Nazwy są znaczące i dokładne
    • kod crufty jest całkowicie usunięty, nie pozostawia się go w pliku osieroconym lub po prostu komentuje
  • Bądź przyjazny dla wyszukiwania
    • cały własny kod źródłowy znajduje się w appkatalogu, dzięki czemu można cduruchomić find / grep / xargs / ag / ack / etc i nie rozpraszać się dopasowaniami innych firm
  • Używaj prostych i oczywistych nazw
    • Npm wydaje się teraz wymagać, aby nazwy pakietów zawierały małe litery. Uważam to za najbardziej okropne, ale muszę śledzić stado, dlatego nazwy plików powinny być używane, kebab-casemimo że nazwa zmiennej w JavaScript musi być taka, camelCaseponieważ- jest znakiem minus w JavaScript.
    • nazwa zmiennej pasuje do nazwy bazowej ścieżki modułu, ale z kebab-casetransformacją nacamelCase
  • Grupuj według sprzężenia, a nie według funkcji
    • To jest głównym odejście od Ruby on Rails konwencji app/views, app/controllers, app/models, etc
    • Funkcje są dodawane do pełnego stosu, więc chcę skupić się na pełnym stosie plików, które są odpowiednie dla mojej funkcji. Kiedy dodam pole numeru telefonu do modelu użytkownika, nie dbam o żaden kontroler inny niż kontroler użytkownika i nie dbam o żaden inny model niż model użytkownika.
    • Zamiast więc edytować 6 plików, z których każdy znajduje się w swoim własnym katalogu i ignorować mnóstwo innych plików w tych katalogach, to repozytorium jest zorganizowane w taki sposób, że wszystkie pliki potrzebne do zbudowania funkcji są kolokowane
    • Z natury MVC widok użytkownika jest sprzężony z kontrolerem użytkownika, który jest połączony z modelem użytkownika. Kiedy więc zmieniam model użytkownika, te 3 pliki często się zmieniają razem, ale kontroler transakcji lub kontroler klienta są oddzielone i dlatego nie są zaangażowane. To samo dotyczy zwykle projektów innych niż MVC.
    • Oddzielenie stylu MVC lub MOVE pod względem tego, który kod wchodzi w który moduł jest nadal wspierany, ale rozkładanie plików MVC do katalogów rodzeństwa jest po prostu denerwujące.
    • Tak więc każdy z moich plików tras ma część tras, którą posiada. Plik w stylu szyn routes.rbjest przydatny, jeśli chcesz przeglądać wszystkie trasy w aplikacji, ale podczas faktycznego budowania funkcji i naprawiania błędów dbasz tylko o trasy odpowiednie dla zmienianego elementu.
  • Przechowuj testy obok kodu
    • To tylko przykład „grupuj przez sprzężenie”, ale chciałem to konkretnie nazwać. Napisałem wiele projektów, w których testy działają w równoległym systemie plików zwanym „testami”, a teraz, kiedy zacząłem umieszczać swoje testy w tym samym katalogu co odpowiadający im kod, nigdy nie wracam. Jest to bardziej modułowe i znacznie łatwiejsze do pracy w edytorach tekstowych i łagodzi wiele nonsensów ścieżki „../../ ..”. W razie wątpliwości wypróbuj kilka projektów i sam zdecyduj. Nie zamierzam robić nic poza tym, aby cię przekonać, że jest lepiej.
  • Zmniejsz sprzężenie poprzeczne dzięki Eventom
    • Łatwo jest pomyśleć „OK, za każdym razem, gdy tworzona jest nowa Umowa, chcę wysłać wiadomość e-mail do wszystkich sprzedawców”, a następnie po prostu umieścić kod, aby wysłać te wiadomości e-mail na trasę, która tworzy oferty.
    • Jednak to połączenie ostatecznie przekształci Twoją aplikację w gigantyczną kulę błota.
    • Zamiast tego DealModel powinien po prostu wywołać zdarzenie „create” i być całkowicie nieświadomy tego, co jeszcze system może zrobić w odpowiedzi na to.
    • Kiedy kodujesz w ten sposób, znacznie łatwiej jest umieścić cały kod związany z użytkownikiem, app/usersponieważ nie ma gniazda szczurów połączonej logiki biznesowej w całym miejscu, która zanieczyszczałaby czystość bazy kodu użytkownika.
  • Przepływ kodu jest możliwy
    • Nie rób magicznych rzeczy. Nie ładuj automatycznie plików z magicznych katalogów w systemie plików. Nie bądźcie szynami. Aplikacja uruchamia się o godzinie app/server.js:1i możesz zobaczyć wszystko, co ładuje i wykonuje, postępując zgodnie z kodem.
    • Nie twórz DSL dla swoich tras. Nie rób głupiego metaprogramowania, gdy nie jest wymagane.
    • Jeśli aplikacja jest tak duża, że robi magicRESTRouter.route(somecontroller, {except: 'POST'})to dla ciebie wielka wygrana na 3 podstawowe app.get, app.put, app.del, rozmowy, jesteś prawdopodobnie budowę monolityczną aplikację, która jest zbyt duża, aby efektywnie pracować. Przygotuj się na DUŻE zwycięstwa, a nie na zamianę 3 prostych linii na 1 linię złożoną.
  • Używaj nazw plików zawierających małe litery kebab

    • Ten format pozwala uniknąć problemów z rozróżnianiem wielkości liter w systemie plików na różnych platformach
    • npm zabrania pisania dużymi literami w nazwach nowych pakietów, i to dobrze z tym działa

      specyfikacja express.js

  • Nie używać app.configure. Jest prawie całkowicie bezużyteczny i po prostu go nie potrzebujesz. Jest w wielu płytkach kuchennych z powodu bezmyślnego copypasta.

  • ZAMÓWIENIE ŚRODKOWEGO OPROGRAMOWANIA I DROGI W SPRAWACH EKSPRESOWYCH !!!
    • Niemal każdy problem z routingiem, jaki widzę podczas przepełnienia stosu, to ekspresowe oprogramowanie pośrednie poza kolejnością
    • Ogólnie rzecz biorąc, chcesz, aby Twoje trasy były rozdzielone i nie polegały na tak dużym porządku
    • Nie używaj app.usedo całej aplikacji, jeśli naprawdę potrzebujesz tego oprogramowania pośredniego tylko na 2 trasy (patrzę na ciebie, body-parser)
    • Upewnij się, że kiedy wszystko zostanie powiedziane i zrobione, masz DOKŁADNIE następujące zamówienie:
      1. Wszelkie bardzo ważne oprogramowanie pośrednie dla całej aplikacji
      2. Wszystkie Twoje trasy i różne pośrednie trasy
      3. TO programy obsługi błędów
  • Niestety, będąc inspirowanym sinatrą, express.js głównie zakłada, że ​​wszystkie twoje trasy będą na miejscu server.jsi będzie jasne, w jaki sposób są one uporządkowane. W przypadku aplikacji średniej wielkości rozdzielanie modułów na osobne trasy jest przyjemne, ale wprowadza niebezpieczeństwo niedziałającego oprogramowania pośredniego

Aplikacja dowiązanie symboliczne

Istnieje wiele podejść przedstawione i omówione obszernie przez społeczność w wielkiej GIST Lepszy lokalny require () ścieżki dla node.js . Mogę wkrótce zdecydować, że wolę „po prostu poradzić sobie z dużą ilością ../../../ ..” lub skorzystać z modlue requFrom. Jednak w tej chwili stosuję sztuczkę z dowiązaniem symbolicznym opisaną poniżej.

Tak więc jednym ze sposobów uniknięcia irytujących ścieżek względnych require("../../../config")jest użycie następującej sztuczki:

  • utwórz dowiązanie symboliczne w module node_modules dla swojej aplikacji
    • cd node_modules && ln -nsf ../app
  • dodaj do git tylko samo dowiązanie symboliczne node_modules / app , a nie cały folder node_modules
    • git dodaj -f moduły_węzła / aplikację
    • Tak, nadal powinieneś mieć „node_modules” w swoim .gitignorepliku
    • Nie, nie powinieneś umieszczać „node_modules” w swoim repozytorium git. Niektóre osoby zalecają to zrobić. Są nieprawidłowe.
  • Teraz możesz wymagać modułów wewnętrznych za pomocą tego prefiksu
    • var config = require("app/config");
    • var DealModel = require("app/deals/deal-model");
  • Zasadniczo sprawia to, że wewnątrz projektu wymaga pracy bardzo podobnie do wymagań dla zewnętrznych modułów npm.
  • Niestety, użytkownicy systemu Windows muszą trzymać się ścieżek względnych katalogu nadrzędnego.

Konfiguracja

Generalnie koduj moduły i klasy, aby oczekiwać, że tylko podstawowy optionsobiekt JavaScript zostanie przekazany. Tylko moduł app/server.jspowinien zostać załadowany app/config.js. Stamtąd może syntezować małe optionsobiekty w celu konfigurowania podsystemów w razie potrzeby, ale łączenie każdego podsystemu z dużym globalnym modułem konfiguracji pełnym dodatkowych informacji jest złym sprzężeniem.

Spróbuj scentralizować tworzenie połączeń DB i przekazywać je do podsystemów, w przeciwieństwie do przekazywania parametrów połączeń i samodzielnego wykonywania połączeń wychodzących przez podsystemy.

NODE_ENV

To kolejny kuszący, ale okropny pomysł przeniesiony z Rails. W aplikacji powinno znajdować się dokładnie 1 miejsce, app/config.jsktóre wygląda na NODE_ENVzmienną środowiskową. Cała reszta powinna przyjąć jawną opcję jako argument konstruktora klasy lub parametr konfiguracyjny modułu.

Jeśli moduł e-mail ma opcję dostarczania wiadomości e-mail (SMTP, logowanie do standardowego wejścia, umieszczanie w kolejce itp.), Powinien wybrać taką opcję, {deliver: 'stdout'}ale absolutnie nie powinien tego sprawdzać NODE_ENV.

Testy

Teraz trzymam moje pliki testowe w tym samym katalogu co odpowiadający im kod i używam konwencji nazewnictwa rozszerzeń nazw plików, aby odróżnić testy od kodu produkcyjnego.

  • foo.js ma kod modułu „foo”
  • foo.tape.js ma testy węzłów dla foo i mieszka w tym samym katalogu
  • foo.btape.js może być używany do testów, które należy wykonać w środowisku przeglądarki

Używam globów systemu plików i find . -name '*.tape.js'polecenia, aby uzyskać dostęp do wszystkich moich testów w razie potrzeby.

Jak zorganizować kod w każdym .jspliku modułu

Zakres tego projektu dotyczy głównie tego, gdzie idą pliki i katalogi, i nie chcę dodawać wiele innych zakresów, ale wspomnę tylko, że mój kod dzielę na 3 odrębne sekcje.

  1. Blok otwierający CommonJS wymaga wywołań zależności stanu
  2. Główny blok kodu czystego JavaScript. Nie ma tutaj zanieczyszczenia CommonJS. Nie odwołuj się do eksportów, modułów ani wymagań.
  3. Blok zamykający CommonJS w celu skonfigurowania eksportu
Peter Lyons
źródło
1
Czego powinienem używać zamiast bodyParser Jeśli mam tylko kilka tras, które z niego korzystają?
Ilan Frumer
3
Znalazłem to, czego szukałem tutaj: stackoverflow.com/questions/12418372/...
Ilan Frumer
1
@wlingke sprawdź gist.github.com/branneman/8048520, aby uzyskać dokładną dyskusję na temat dostępnych podejść do tego problemu.
Peter Lyons,
@peterLyons Dzięki za udostępnienie tego. Po przeczytaniu myślę, że napiszę skrypt startowy. Dzięki!
wlingke
2
w odniesieniu do sztuczki dowiązania symbolicznego do aplikacji , jest ten mały moduł, który sprawia, że ​​wszystkie problemy znikają
Hayko Koryun
157

AKTUALIZACJA (2013-10-29) : Proszę zobaczyć moją inną odpowiedź, która zawiera JavaScript zamiast CoffeeScript przez popularne żądanie, a także repozytorium github repo i obszerny plik README opisujący moje najnowsze rekomendacje na ten temat.

Config

To co robisz jest w porządku. Lubię mieć własną przestrzeń nazw konfiguracji skonfigurowaną w pliku najwyższego poziomu config.coffeez taką zagnieżdżoną przestrzenią nazw.

#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}"

Jest to przyjazne dla edycji sysadmin. Potem, gdy potrzebuję czegoś, na przykład informacji o połączeniu DB, jest to

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

Trasy / Kontrolery

Lubię zostawiać trasy z kontrolerami i organizować je w app/controllerspodkatalogu. Następnie mogę je załadować i pozwolić im dodać dowolne trasy, których potrzebują.

W moim app/server.coffee pliku coffeescript wykonuję:

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

Mam więc pliki takie jak:

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

I na przykład w moim kontrolerze domen mam taką setupfunkcję.

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

Wyświetlenia

Widoki app/viewsstają się zwyczajowym miejscem. Tak to układam.

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

Pliki statyczne

Idź w public podkatalogu.

Github / Semver / NPM

Umieść plik README.md w swoim katalogu głównym git repo dla github.

Umieść plik package.json z semantycznym numerem wersji w katalogu głównym git repo dla NPM.

Peter Lyons
źródło
1
Hej Peter! Naprawdę podoba mi się to podejście, którego szukasz. Pracuję nad budowaniem ekspresowego projektu i naprawdę chciałbym robić rzeczy we właściwy sposób, niż tylko zhakować go i rozłożyć. Byłoby wspaniale, gdybyś miał przykładowe repo na github i / lub post na blogu.
suVasH .....
4
To repozytorium zawiera wiele wzorów, które można wykorzystać jako odniesienie: github.com/focusaurus/peterlyons.com
Peter Lyons
75
Skrypt do kawy utrudnia czytanie: / Czy jest szansa na waniliową edycję JS? Dzięki
toasted_flakes
1
Dziękuję za tę odpowiedź. Po prostu próbuję się tym zająć. Jak uzyskać dostęp do innych kontrolerów w innym (np. W funkcji konfiguracji jak wyżejapp.put route, api.needId
chmanie 24.10.2013
@PeterLyons: hej, widziałem twój kod źródłowy, ale nie mam pojęcia, jak zrobić tryb kompilacji, już zainstalowałem Goi dołączam binplik do struktury. Jak uruchomić ten goplik bin?
user2002495,
51

Poniżej znajduje się pełna odpowiedź Petera Lyonsa, przeniesiona do waniliowej JS z Coffeescript, zgodnie z żądaniem kilku innych. Odpowiedź Piotra jest bardzo zdolna i każdy, kto głosuje na moją odpowiedź, również powinien głosować na jego odpowiedź.


Config

To co robisz jest w porządku. Lubię mieć własną przestrzeń nazw konfiguracji skonfigurowaną w pliku najwyższego poziomu config.jsz taką zagnieżdżoną przestrzenią nazw.

// 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}"
};

Jest to przyjazne dla edycji sysadmin. Potem, gdy potrzebuję czegoś, na przykład informacji o połączeniu DB, jest to

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

Trasy / Kontrolery

Lubię zostawiać trasy z kontrolerami i organizować je w app/controllerspodkatalogu. Następnie mogę je załadować i pozwolić im dodać dowolne trasy, których potrzebują.

W moim app/server.jspliku javascript robię:

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

Mam więc pliki takie jak:

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

I na przykład w moim kontrolerze domen mam taką setupfunkcję.

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);
  });
}

Wyświetlenia

Widoki app/viewsstają się zwyczajowym miejscem. Tak to układam.

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

Pliki statyczne

Przejdź dopublic podkatalogu.

Github / Semver / NPM

Umieść plik README.md w swoim katalogu głównym git repo dla github.

Umieść plik package.json z semantycznym numerem wersji w katalogu głównym git repo dla NPM.

dthree
źródło
43

Moje pytanie zostało wprowadzone w kwietniu 2011 roku, jest cicho stare. W tym czasie mogłem poprawić swoje wrażenia z Express.js i jak zaprojektować aplikację napisaną przy użyciu tej biblioteki. Podzielę się tutaj moim doświadczeniem.

Oto moja struktura katalogów:

├── 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

Celem app.jspliku jest bootstrap aplikacji expressjs. Ładuje moduł konfiguracji, moduł rejestratora, czeka na połączenie z bazą danych, ... i uruchamia serwer ekspresowy.

'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();
});

trasy /

Katalog tras ma index.jsplik. Jego celem jest wprowadzenie pewnego rodzaju magii do ładowania wszystkich innych plików w routes/katalogu. Oto implementacja:

/**
 * 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);
  });
};

Dzięki temu modułowi tworzenie nowej definicji trasy i jej implementacji jest naprawdę łatwe. Na przykład hello.js:

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

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

Każdy moduł trasy jest samodzielny .

Sandro Munda
źródło
Czy używasz generatora do stworzenia tej struktury?
Ashish
18

Lubię używać globalnej „aplikacji”, zamiast eksportować funkcję itp

tjholowaychuk
źródło
Zdecydowałem się na porady od twórców :) BTW, czy możesz podać nam kod?
wcielono
Zgadza się. w tych aplikacjach, które widzisz - github.com/visionmedia/screenshot-app
diproart
17

Myślę, że to świetny sposób, aby to zrobić. Nie tylko ekspresowe, ale widziałem całkiem sporo projektów node.js na github robiących to samo. Wyciągają parametry konfiguracyjne + mniejsze moduły (w niektórych przypadkach każdy identyfikator URI) są podzielone na osobne pliki.

Poleciłbym przejrzenie konkretnych projektów na github, aby uzyskać pomysł. IMO sposób w jaki robisz jest poprawny.

Neebz
źródło
16

jest teraz koniec 2015 roku i po 3 latach rozwijania mojej struktury oraz w małych i dużych projektach. Wniosek?

Nie rób jednego dużego MVC, ale rozdziel go na moduły

Więc...

Dlaczego?

  • Zwykle jeden działa na jednym module (np. Produkty), który można zmieniać niezależnie.

  • Możesz ponownie wykorzystać moduły

  • Możesz to przetestować osobno

  • Możesz go wymienić osobno

  • Mają jasne (stabilne) interfejsy

    - Jeśli działało wielu programistów, separacja modułów pomaga

Projekt nodebootstrap ma podobne podejście do mojej ostatecznej struktury. ( github )

Jak wygląda ta struktura?

  1. Małe, zamknięte moduły , każdy z osobnym MVC

  2. Każdy moduł ma pakiet.json

  3. Testowanie jako część struktury (w każdym module)

  4. Globalna konfiguracja , biblioteki i usługi

  5. Zintegrowany Docker, Cluster, na zawsze

Przegląd folderów (zobacz folder lib dla modułów):

struktura nodebootstrap

Simon Fakir
źródło
3
Przydałoby się zaktualizować obraz przeglądu folderów wraz z rozwiniętymi poszczególnymi modułami, jako przykład ich struktury.
youngrrrr
8

Daję strukturę folderów w stylu MVC, proszę znaleźć poniżej.

W naszych dużych i średnich aplikacjach internetowych zastosowaliśmy poniższą strukturę folderów.

 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

Stworzyłem jeden moduł npm do generowania struktury folderów express mvc.

Poniżej znajduje się https://www.npmjs.com/package/express-mvc-generator

Wystarczy proste kroki, aby wygenerować i korzystać z tych modułów.

i) zainstaluj moduł npm install express-mvc-generator -g

ii) sprawdź opcje express -h

iii) Wygeneruj ekspresową strukturę mvc express myapp

iv) Zainstaluj zależności npm install:

v) Otwórz plik config / database.js. Skonfiguruj bazę danych mongo.

vi) Uruchom aplikację node applubnodemon app

vii) Sprawdź adres URL http: // localhost: 8042 / signup LUB http: // yourip: 8042 / signup

Raja Rama Mohan Thavalam
źródło
7

Minęło sporo czasu od ostatniej odpowiedzi na to pytanie, a Express wydał również niedawno wersję 4, która dodała kilka przydatnych rzeczy do uporządkowania struktury aplikacji.

Poniżej znajduje się długi post na blogu na temat najlepszych praktyk dotyczących strukturyzacji aplikacji Express. http://www.terlici.com/2014/08/25/best-practices-express-structure.html

Istnieje również repozytorium GitHub, które stosuje porady zawarte w artykule. Jest zawsze na bieżąco z najnowszą wersją Express.
https://github.com/terlici/base-express

Stefan
źródło
7

Nie sądzę, aby dobrym rozwiązaniem było dodawanie tras do konfiguracji. Lepsza struktura może być taka:

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...

Tak więc products.js i users.js będą zawierać wszystkie twoje trasy, wszystkie będą logiczne.

programista
źródło
6

Cóż, umieściłem swoje trasy jako plik json, który czytałem na początku, aw pętli for w app.js ustawiłem trasy. Route.json zawiera widok, który powinien zostać wywołany, oraz klucz wartości, które zostaną wysłane na trasę.
Działa to w wielu prostych przypadkach, ale musiałem ręcznie utworzyć kilka tras dla specjalnych przypadków.

TiansHUo
źródło
6

Napisałem post dokładnie na ten temat. Zasadniczo korzysta z pliku, routeRegistrarktóry iteruje pliki w folderze /controllerswywołującym jego funkcję init. Funkcja initprzyjmuje appzmienną ekspresową jako parametr, dzięki czemu możesz zarejestrować swoje trasy tak, jak chcesz.

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);
Renato Gama
źródło
5

Może to być interesujące:

https://github.com/flatiron/nconf

Hierarchiczna konfiguracja node.js z plikami, zmiennymi środowiskowymi, argumentami wiersza poleceń i scalaniem obiektów atomowych.

Ulisses V.
źródło
4

1) Twój system plików projektu Express może:

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

app.js - globalny kontener aplikacji

2) Główny plik modułu (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) Podłącz moduł w głównej aplikacji.js

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

4) Przykładowa logika

lib/login
lib/db
lib/config
lib/users
lib/verify
lib/
   /api/ 
   ...
lib/
   /admin/
      /users/
      /settings/
      /groups/
...
  • Najlepsze do testowania
  • Najlepsze dla skali
  • Oddzielnie zależy od modułu
  • Grupowanie trasy według funkcjonalności (lub modułów)

tj mówi / pokaż na Vimeo ciekawy pomysł, jak modularyzować aplikację ekspresową - modułowe aplikacje internetowe z Node.js i Express . Mocny i prosty.

diproart
źródło
4

http://locomotivejs.org/ zapewnia sposób na uporządkowanie aplikacji zbudowanej z Node.js i Express.

Ze strony:

„Lokomotywa to platforma internetowa dla Node.js. Lokomotywa obsługuje wzorce MVC, trasy RESTful i konwencje dotyczące konfiguracji, jednocześnie płynnie integrując się z dowolnymi silnikami baz danych i szablonów. Lokomotywa opiera się na Expressie, zachowując moc i prostotę, której można się spodziewać z węzła. ”

Ben Mordue
źródło
3

Niedawno przyjęłam moduły jako niezależne mini-aplikacje.

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

Teraz dla dowolnego routingu modułów (# .js) widoki (* .ejs), js, css i zasoby są obok siebie. routing submodułu jest ustawiony w nadrzędnym # .js z dwoma dodatkowymi liniami

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

W ten sposób możliwe są nawet podmoduły.

Nie zapomnij ustawić widoku na katalog src

app.set('views', path.join(__dirname, 'src'));
zevero
źródło
jakikolwiek link do github o takiej strukturze zainteresowany widzeniem, w jaki sposób ładowane są trasy, widoki i modele
Muhammad Umer
Myślę, że wszystko zostało wyjaśnione. Trasy to tylko klasyczne trasy ekspresowe. Widoki należy ładować z prefiksem nazw modułów, modele ładować przez odniesienie do ścieżki względnej.
zevero
W ostatnim wierszu ustawiłem widok na katalog src. Od tego momentu wszystkie widoki są dostępne względem katalogu src. Nic fajnego.
zevero
1

Tak wygląda większość struktury mojego katalogu projektów ekspresowych.

Zwykle express dirnameinicjuję projekt, wybaczam moje lenistwo, ale jest on bardzo elastyczny i można go rozbudowywać. PS - musisz się na to zdobyć express-generator(dla tych, którzy go szukają sudo npm install -g express-generator, sudo, ponieważ instalujesz go globalnie)

|-- 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

Musisz się zastanawiać, dlaczego pliki .env? Ponieważ działają! Używam dotenvmodułu w moich projektach (ostatnio) i działa! Wpisz te 2 stwierdzenia w app.jslubwww

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

I kolejny wiersz, aby szybko ustawić /bower_componentswyświetlanie treści statycznych w zasobie/ext

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

Prawdopodobnie może być odpowiedni dla osób, które chcą używać Express i Angular razem lub po prostu ekspres bez tej javascriptshierarchii, oczywiście.

Nitesh Oswal
źródło
1

Moja struktura ekspresowa 4. https://github.com/odirleiborgert/borgert-express-boilerplate

Pakiety

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

Struktura

|-- app
    |-- controllers
    |-- helpers
    |-- middlewares
    |-- models
    |-- routes
    |-- services
|-- bin
|-- logs
|-- node_modules
|-- public
    |-- components
    |-- images
    |-- javascripts
    |-- stylesheets
|-- views
|-- .env
|-- .env-example
|-- app.js
|-- README.md
Odirlei Borgert
źródło
0

Prosty sposób na uporządkowanie aplikacji ekspresowej:

  • W main index.js należy zachować następującą kolejność.

    wszystkie app.set powinny być pierwsze.

    wszystko app.use powinno być drugie.

    następnie inne api z ich funkcjami lub route-kontynuuj w innych plikach

    Przykro mi

    app.use („/ password”, passwordApi);

    app.use („/ user”, userApi);

    app.post („/ token”, passport.createToken);

    app.post („/ logout”, passport.logout)

Snivio
źródło
0

Najlepszy sposób na strukturę MVC dla projektu ExpressJs z kierownicą i 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
Manishkumar Bhavnani
źródło
@ sandro-munda proszę sprawdzić
Manishkumar Bhavnani