ExpressJS Làm thế nào để cấu trúc một ứng dụng?


527

Tôi đang sử dụng khung web ExpressJS cho NodeJS.

Những người sử dụng ExpressJS đặt môi trường của họ (phát triển, sản xuất, thử nghiệm ...), các tuyến đường của họ, v.v. app.js . Tôi nghĩ rằng đó không phải là một cách hay vì khi bạn có một ứng dụng lớn, app.js quá lớn!

Tôi muốn có cấu trúc thư mục này:

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

Đây là mã của tôi:

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

cấu hình / môi trường.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());
    });
};

cấu hình / tuyến.js

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

Mã của tôi hoạt động tốt và tôi nghĩ rằng cấu trúc của các thư mục là đẹp. Tuy nhiên, mã phải được điều chỉnh và tôi không chắc rằng nó tốt / đẹp.

Là tốt hơn để sử dụng cấu trúc thư mục của tôi và điều chỉnh mã hoặc chỉ đơn giản là sử dụng một tệp (app.js)?

Cảm ơn lời khuyên của bạn!


Có phải các vấn đề hiệu suất của việc làm theo cách này vẫn còn lẩn khuất? Tôi nhớ đã đọc ở đâu đó (có thể là nhóm thể hiện) rằng khi bạn tách mọi thứ như thế này, bạn sẽ mất rất nhiều hiệu suất. Một cái gì đó như reqs / giây của bạn sẽ giảm một lượng đáng chú ý, gần như là một lỗi.
AntelopeSalad

2
Đó là từ nhóm Express Google. Đây là liên kết: Groups.google.com/group/express-js/browse_thread/thread/iêu
AntelopeSalad

52
Không, điều này là không đúng sự thật
tjholowaychuk

Câu trả lời:


306

OK, đã được một thời gian và đây là một câu hỏi phổ biến, vì vậy tôi đã đi trước và tạo ra một kho lưu trữ github giàn giáo với mã JavaScript và một README dài về cách tôi muốn cấu trúc một ứng dụng express.js cỡ trung bình.

Focusaurus / express_code_itectture là repo với mã mới nhất cho việc này. Kéo yêu cầu chào mừng.

Đây là ảnh chụp nhanh của README vì stackoverflow không giống như câu trả lời chỉ là một liên kết. Tôi sẽ thực hiện một số cập nhật vì đây là một dự án mới mà tôi sẽ tiếp tục cập nhật, nhưng cuối cùng thì repo github sẽ là nơi cập nhật cho thông tin này.


Cấu trúc mã nhanh

Dự án này là một ví dụ về cách tổ chức một ứng dụng web express.js cỡ trung bình.

Hiện tại ít nhất là v4,14 tháng 12 năm 2016

Xây dựng tình trạng

js-tiêu chuẩn-phong cách

Ứng dụng của bạn lớn như thế nào?

Các ứng dụng web không giống nhau và theo tôi, không có một cấu trúc mã nào được áp dụng cho tất cả các ứng dụng express.js.

Nếu ứng dụng của bạn nhỏ, bạn không cần cấu trúc thư mục sâu như ví dụ ở đây. Chỉ cần đơn giản và dán một số .jstệp trong thư mục gốc của kho lưu trữ của bạn và bạn đã hoàn tất. Võngà.

Nếu ứng dụng của bạn rất lớn, đến một lúc nào đó bạn cần chia nó thành các gói npm riêng biệt. Nói chung, cách tiếp cận node.js dường như ưu tiên nhiều gói nhỏ, ít nhất là cho các thư viện và bạn nên xây dựng ứng dụng của mình bằng cách sử dụng một số gói npm vì điều đó bắt đầu có ý nghĩa và biện minh cho chi phí. Vì vậy, khi ứng dụng của bạn phát triển và một phần mã trở nên rõ ràng có thể tái sử dụng bên ngoài ứng dụng của bạn hoặc là một hệ thống con rõ ràng, hãy chuyển nó sang kho git của chính nó và biến nó thành một gói npm độc lập.

Vì vậy, trọng tâm của dự án này là minh họa một cấu trúc hoàn toàn khả thi cho một ứng dụng cỡ trung bình.

Kiến trúc tổng thể của bạn là gì

Có nhiều cách tiếp cận để xây dựng một ứng dụng web, chẳng hạn như

  • Server Side MVC là một Ruby trên Rails
  • Ứng dụng một trang kiểu a la MongoDB / Express / Angular / Node (MEAN)
  • Trang web cơ bản với một số hình thức
  • Mô hình / Hoạt động / Lượt xem / Phong cách sự kiện theo kiểu MVC đã chết, đã đến lúc chuyển sang
  • và nhiều người khác cả hiện tại và lịch sử

Mỗi trong số này phù hợp độc đáo vào một cấu trúc thư mục khác nhau. Đối với mục đích của ví dụ này, nó chỉ là giàn giáo và không phải là một ứng dụng hoạt động hoàn toàn, nhưng tôi giả sử các điểm kiến ​​trúc chính sau:

  • Trang web có một số trang / mẫu tĩnh truyền thống
  • Phần "ứng dụng" của trang web được phát triển theo kiểu Ứng dụng một trang
  • Ứng dụng hiển thị API kiểu REST / JSON cho trình duyệt
  • Ứng dụng này mô hình một miền kinh doanh đơn giản, trong trường hợp này, đó là một ứng dụng đại lý xe hơi

Còn Ruby on Rails thì sao?

Đây sẽ là một chủ đề xuyên suốt dự án này, nhiều ý tưởng thể hiện trong Ruby on Rails và các quyết định "Công ước về cấu hình" mà họ đã thông qua, mặc dù được chấp nhận và sử dụng rộng rãi, nhưng thực sự không hữu ích và đôi khi trái ngược với những gì kho lưu trữ này đề nghị.

Quan điểm chính của tôi ở đây là có các nguyên tắc cơ bản để tổ chức mã và dựa trên các nguyên tắc đó, các quy ước của Ruby on Rails có ý nghĩa (chủ yếu) đối với cộng đồng Ruby on Rails. Tuy nhiên, chỉ cần vô tư kích hoạt những quy ước đó bỏ lỡ điểm. Khi bạn tìm hiểu các nguyên tắc cơ bản, TẤT CẢ các dự án của bạn sẽ được tổ chức tốt và rõ ràng: kịch bản shell, trò chơi, ứng dụng di động, dự án doanh nghiệp, thậm chí thư mục chính của bạn.

Đối với cộng đồng Rails, họ muốn có thể có một nhà phát triển Rails duy nhất chuyển từ ứng dụng này sang ứng dụng này sang ứng dụng khác và làm quen và thoải mái với nó mỗi lần. Điều này rất có ý nghĩa nếu bạn là 37 tín hiệu hoặc Pivotal Labs và có lợi ích. Trong thế giới JavaScript phía máy chủ, các đặc điểm chung chỉ là cách mọi thứ ở phía tây hoang dã hơn và chúng ta không thực sự có vấn đề với điều đó. Đó là cách chúng tôi lăn. Chúng ta đã quen với nó. Ngay cả trong express.js, đó là người thân của Sinatra, không phải Rails và việc thực hiện các quy ước từ Rails thường không giúp được gì. Tôi thậm chí sẽ nói Nguyên tắc về Công ước về Cấu hình .

Nguyên tắc và động lực cơ bản

  • Có thể quản lý tinh thần
    • Bộ não chỉ có thể đối phó và suy nghĩ về một số lượng nhỏ những điều liên quan cùng một lúc. Đó là lý do tại sao chúng tôi sử dụng các thư mục. Nó giúp chúng ta đối phó với sự phức tạp bằng cách tập trung vào các phần nhỏ.
  • Có kích thước phù hợp
    • Đừng tạo "Biệt thự biệt thự" khi chỉ có 1 tệp tất cả 3 thư mục. Bạn có thể thấy điều này xảy ra trong Thực tiễn tốt nhất Ansible giúp các dự án nhỏ tạo ra hơn 10 thư mục để chứa hơn 10 tệp khi 1 thư mục có 3 tệp sẽ phù hợp hơn nhiều. Bạn không lái xe buýt đi làm (trừ khi bạn là tài xế xe buýt, nhưng ngay cả khi bạn lái xe buýt AT không hoạt động), vì vậy đừng tạo cấu trúc hệ thống tệp không được chứng minh bằng các tệp thực tế bên trong chúng .
  • Là mô-đun nhưng thực dụng
    • Cộng đồng nút ủng hộ các mô-đun nhỏ. Bất kỳ thứ gì có thể tách rời khỏi ứng dụng của bạn hoàn toàn nên được trích xuất thành một mô-đun để sử dụng nội bộ hoặc được xuất bản công khai vào npm. Tuy nhiên, đối với các ứng dụng cỡ trung bình là phạm vi ở đây, chi phí hoạt động này có thể thêm tedium vào quy trình làm việc của bạn mà không có giá trị tương xứng. Vì vậy, tại thời điểm bạn có một số mã được bao gồm nhưng không đủ để biện minh cho một mô-đun npm hoàn toàn riêng biệt, chỉ cần coi đó là một " mô-đun proto " với kỳ vọng rằng khi vượt qua ngưỡng kích thước nào đó, nó sẽ được trích xuất.
    • Một số người như @ Hij1nx thậm chí bao gồm một app/node_modulesthư mục và có package.jsoncác tệp trong các thư mục mô-đun proto để tạo điều kiện cho quá trình chuyển đổi đó và hoạt động như một lời nhắc nhở.
  • Dễ dàng xác định mã
    • Đưa ra một tính năng để xây dựng hoặc sửa lỗi, mục tiêu của chúng tôi là nhà phát triển không gặp khó khăn trong việc định vị các tệp nguồn liên quan.
    • Tên có ý nghĩa và chính xác
    • mã crufty được loại bỏ hoàn toàn, không bị bỏ lại trong một tập tin mồ côi hoặc chỉ nhận xét
  • Thân thiện với tìm kiếm
    • tất cả mã nguồn của bên thứ nhất đều có trong appthư mục để bạn có thể cdchạy run / grep / xargs / ag / ack / etc và không bị phân tâm bởi các trận đấu của bên thứ ba
  • Sử dụng cách đặt tên đơn giản và rõ ràng
    • npm bây giờ dường như yêu cầu tất cả các tên gói chữ thường. Tôi thấy điều này chủ yếu là khủng khiếp nhưng tôi phải theo đàn, do đó tên tệp nên sử dụng kebab-casemặc dù tên biến trong đó phải là camelCasevì JavaScript- dấu trừ trong JavaScript.
    • tên biến khớp với tên cơ sở của đường dẫn mô-đun, nhưng được kebab-casechuyển đổi thànhcamelCase
  • Nhóm theo khớp nối, không theo chức năng
    • Đây là một sự khởi đầu lớn từ Ruby on Rails triệu tập app/views, app/controllers, app/models, vv
    • Các tính năng được thêm vào một ngăn xếp đầy đủ, vì vậy tôi muốn tập trung vào một ngăn xếp đầy đủ các tệp có liên quan đến tính năng của tôi. Khi tôi thêm trường số điện thoại vào mô hình người dùng, tôi không quan tâm đến bất kỳ bộ điều khiển nào ngoài bộ điều khiển người dùng và tôi không quan tâm đến bất kỳ mô hình nào ngoài mô hình người dùng.
    • Vì vậy, thay vì chỉnh sửa 6 tệp trong mỗi thư mục của riêng họ và bỏ qua hàng tấn tệp khác trong các thư mục đó, kho lưu trữ này được tổ chức sao cho tất cả các tệp tôi cần để xây dựng một tính năng được tạo ra
    • Theo bản chất của MVC, khung nhìn người dùng được ghép nối với bộ điều khiển người dùng được ghép nối với mô hình người dùng. Vì vậy, khi tôi thay đổi mô hình người dùng, 3 tệp đó sẽ thường thay đổi cùng nhau, nhưng bộ điều khiển giao dịch hoặc bộ điều khiển khách hàng được tách rời và do đó không liên quan. Áp dụng tương tự cho các thiết kế không phải MVC thường là tốt.
    • Việc tách rời kiểu MVC hoặc MOVE về mặt mã đi theo mô-đun nào vẫn được khuyến khích, nhưng việc truyền bá các tệp MVC ra các thư mục anh chị em chỉ gây phiền nhiễu.
    • Do đó, mỗi tệp tuyến đường của tôi có một phần các tuyến đường mà nó sở hữu. routes.rbTệp kiểu đường ray rất tiện dụng nếu bạn muốn có tổng quan về tất cả các tuyến đường trong ứng dụng, nhưng khi thực sự xây dựng các tính năng và sửa lỗi, bạn chỉ quan tâm đến các tuyến đường có liên quan đến đoạn bạn đang thay đổi.
  • Lưu trữ các bài kiểm tra bên cạnh mã
    • Đây chỉ là một ví dụ của "nhóm bằng cách ghép", nhưng tôi muốn gọi nó một cách cụ thể. Tôi đã viết nhiều dự án trong đó các bài kiểm tra nằm trong một hệ thống tệp song song được gọi là "các bài kiểm tra" và bây giờ tôi đã bắt đầu đặt các bài kiểm tra của mình vào cùng thư mục với mã tương ứng của chúng, tôi sẽ không bao giờ quay lại. Đây là mô-đun nhiều hơn và dễ dàng hơn để làm việc với các trình soạn thảo văn bản và giảm bớt rất nhiều đường dẫn "../../ ..". Nếu bạn nghi ngờ, hãy thử nó trong một vài dự án và tự quyết định. Tôi sẽ không làm bất cứ điều gì ngoài điều này để thuyết phục bạn rằng nó tốt hơn.
  • Giảm khớp nối chéo với Sự kiện
    • Thật dễ dàng để nghĩ rằng "OK, bất cứ khi nào một Giao dịch mới được tạo, tôi muốn gửi email đến tất cả các Nhân viên bán hàng", và sau đó chỉ cần đặt mã để gửi các email đó theo lộ trình tạo giao dịch.
    • Tuy nhiên, khớp nối này cuối cùng sẽ biến ứng dụng của bạn thành một quả bóng bùn khổng lồ.
    • Thay vào đó, DealModel chỉ nên thực hiện một sự kiện "tạo" và hoàn toàn không biết hệ thống có thể làm gì khác để đáp ứng với điều đó.
    • Khi bạn viết mã theo cách này, việc đưa tất cả các mã liên quan đến người dùng trở nên khả thi hơn rất nhiều app/usersvì không có tổ hợp logic kinh doanh kết hợp nào ở khắp mọi nơi gây ô nhiễm độ tinh khiết của cơ sở mã người dùng.
  • Dòng mã được theo dõi
    • Đừng làm những điều kỳ diệu. Không tự động tải các tệp từ các thư mục ma thuật trong hệ thống tệp. Đừng là Rails. Ứng dụng bắt đầu vào app/server.js:1và bạn có thể thấy mọi thứ nó tải và thực thi bằng cách làm theo mã.
    • Đừng tạo DSL cho các tuyến đường của bạn. Đừng làm siêu lập trình ngớ ngẩn khi nó không được gọi.
    • Nếu ứng dụng của bạn là quá lớn mà làm magicRESTRouter.route(somecontroller, {except: 'POST'})là một chiến thắng lớn cho bạn hơn 3 cơ bản app.get, app.put, app.del, gọi điện, có lẽ bạn đang xây dựng một ứng dụng nguyên khối đó là quá lớn để làm việc hiệu quả trên. Thích thú với chiến thắng LỚN, không phải để chuyển đổi 3 dòng đơn giản thành 1 dòng phức tạp.
  • Sử dụng tên tệp trường hợp thấp hơn

    • Định dạng này tránh các vấn đề về độ nhạy trường hợp hệ thống tệp trên các nền tảng
    • npm cấm viết hoa trong tên gói mới và điều này hoạt động tốt với điều đó

      chi tiết cụ thể của express.js

  • Đừng sử dụng app.configure. Nó gần như hoàn toàn vô dụng và bạn không cần nó. Đó là trong rất nhiều nồi hơi do copypasta vô tâm.

  • ĐẶT HÀNG CỦA PHẦN MỀM TRUNG TÂM VÀ ROUTES TRONG VẤN ĐỀ RPR RÀNG !!!
    • Hầu như mọi vấn đề định tuyến tôi thấy trên stackoverflow là phần mềm trung gian cấp tốc không theo thứ tự
    • Nói chung, bạn muốn các tuyến đường của mình tách rời và không phụ thuộc vào đơn hàng nhiều như vậy
    • Không sử dụng app.usecho toàn bộ ứng dụng của bạn nếu bạn thực sự chỉ cần phần mềm trung gian đó cho 2 tuyến đường (tôi đang nhìn bạn, body-parser)
    • Hãy chắc chắn rằng khi tất cả được nói và thực hiện, bạn đã CHÍNH XÁC thứ tự này:
      1. Bất kỳ phần mềm trung gian ứng dụng siêu quan trọng
      2. Tất cả các tuyến đường của bạn và các loại trung gian tuyến
      3. THEN xử lý lỗi
  • Đáng buồn thay, được lấy cảm hứng từ sinatra, express.js chủ yếu giả định tất cả các tuyến đường của bạn sẽ vào server.jsvà sẽ rõ ràng cách chúng được đặt hàng. Đối với một ứng dụng cỡ trung bình, việc chia nhỏ các mô-đun các tuyến riêng biệt là tốt, nhưng nó giới thiệu sự nguy hiểm của phần mềm trung gian không theo thứ tự

Thủ thuật symlink ứng dụng

Có rất nhiều cách tiếp cận nêu và thảo luận tại chiều dài của cộng đồng trong các ý chính lớn Better địa phương yêu cầu () đường dẫn cho Node.js . Tôi có thể sớm quyết định thích "chỉ giao dịch với nhiều ../../../ .." hoặc sử dụng modlue requestFrom . Tuy nhiên, hiện tại, tôi đã sử dụng thủ thuật symlink chi tiết bên dưới.

Vì vậy, một cách để tránh yêu cầu nội bộ dự án với các đường dẫn tương đối khó chịu như require("../../../config")là sử dụng thủ thuật sau:

  • tạo một liên kết tượng trưng dưới node_modules cho ứng dụng của bạn
    • cd node_modules && ln -nsf ../app
  • chỉ thêm chính symlink node_modules / app , chứ không phải toàn bộ thư mục node_modules, vào git
    • git add -f node_modules / ứng dụng
    • Có, bạn vẫn nên có "node_modules" trong .gitignoretệp của mình
    • Không, bạn không nên đặt "node_modules" vào kho git của mình. Một số người sẽ khuyên bạn nên làm điều này. Họ không chính xác.
  • Bây giờ bạn có thể yêu cầu các mô-đun nội bộ dự án bằng cách sử dụng tiền tố này
    • var config = require("app/config");
    • var DealModel = require("app/deals/deal-model");
  • Về cơ bản, điều này làm cho nội bộ dự án đòi hỏi công việc rất giống với yêu cầu cho các mô-đun npm bên ngoài.
  • Xin lỗi, người dùng Windows, bạn cần phải gắn bó với các đường dẫn tương đối của thư mục cha.

Cấu hình

Nói chung, các mô-đun mã và các lớp chỉ mong đợi một optionsđối tượng JavaScript cơ bản được truyền vào. Chỉ app/server.jsnên tải app/config.jsmô-đun. Từ đó, nó có thể tổng hợp các optionsđối tượng nhỏ để cấu hình các hệ thống con khi cần, nhưng ghép mọi hệ thống con với một mô-đun cấu hình toàn cầu lớn chứa đầy thông tin bổ sung là khớp nối xấu.

Cố gắng tập trung vào việc tạo các kết nối DB và chuyển chúng vào các hệ thống con trái ngược với việc truyền các tham số kết nối và có các hệ thống con tự tạo các kết nối đi.

NODE_ENV

Đây là một ý tưởng hấp dẫn nhưng khủng khiếp khác được thực hiện từ Rails. Cần có chính xác 1 vị trí trong ứng dụng của bạn, app/config.jsđó là xem NODE_ENVbiến môi trường. Mọi thứ khác nên có một tùy chọn rõ ràng làm tham số cấu trúc mô đun hoặc tham số cấu hình mô đun.

Nếu mô-đun email có một tùy chọn về cách gửi email (SMTP, đăng nhập vào thiết bị xuất chuẩn, đặt hàng đợi, v.v.), thì nó sẽ có một tùy chọn như thế {deliver: 'stdout'}nhưng tuyệt đối không nên kiểm tra NODE_ENV.

Xét nghiệm

Bây giờ tôi giữ các tệp thử nghiệm của mình trong cùng thư mục với mã tương ứng của chúng và sử dụng các quy ước đặt tên mở rộng tên tệp để phân biệt các thử nghiệm với mã sản xuất.

  • foo.js có mã của mô-đun "foo"
  • foo.tape.js có các bài kiểm tra dựa trên nút cho foo và sống trong cùng một thư mục
  • foo.btape.js có thể được sử dụng cho các bài kiểm tra cần thực hiện trong môi trường trình duyệt

Tôi sử dụng các hệ thống tập tin và find . -name '*.tape.js'lệnh để có quyền truy cập vào tất cả các bài kiểm tra của tôi khi cần thiết.

Cách tổ chức mã trong mỗi .jstệp mô-đun

Phạm vi của dự án này chủ yếu là về nơi các tệp và thư mục đi đến và tôi không muốn thêm nhiều phạm vi khác, nhưng tôi sẽ chỉ đề cập rằng tôi sắp xếp mã của mình thành 3 phần riêng biệt.

  1. Mở khối CommonJS yêu cầu các cuộc gọi đến các phụ thuộc trạng thái
  2. Khối mã chính của JavaScript thuần. Không có ô nhiễm CommonJS ở đây. Không tham chiếu xuất khẩu, mô-đun, hoặc yêu cầu.
  3. Đóng khối CommonJS để thiết lập xuất khẩu

1
Tôi nên sử dụng cái gì thay cho bodyParser Nếu tôi chỉ có vài tuyến sử dụng nó?
Ilan Frumer

3
Tôi đã tìm thấy những gì tôi đang tìm kiếm ở đây: stackoverflow.com/questions/12418372/
Kẻ

1
@wlingke hãy xem gist.github.com/branneman/8048520 để thảo luận kỹ về các phương pháp tiếp cận có sẵn cho vấn đề đó.
Peter Lyons

@peterLyons Cảm ơn bạn đã chia sẻ điều đó. Sau khi đọc qua, tôi nghĩ rằng tôi sẽ viết một kịch bản khởi động. Cảm ơn!
wlingke

2
liên quan đến các thủ thuật Ứng dụng liên kết tượng trưng , có này mô-đun nhỏ mà làm cho tất cả các vấn đề đi xa
Hayko Koryun

157

CẬP NHẬT (2013-10-29) : Vui lòng xem câu trả lời khác của tôi cũng có JavaScript thay vì CoffeeScript theo yêu cầu phổ biến cũng như repo github soạn sẵn và một README chi tiết đề xuất mới nhất của tôi về chủ đề này.

Cấu hình

Những gì bạn đang làm là tốt. Tôi muốn có không gian tên cấu hình của riêng mình được thiết lập trong một config.coffeetệp cấp cao nhất với một không gian tên lồng nhau như thế này.

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

Điều này là thân thiện để chỉnh sửa sysadmin. Sau đó, khi tôi cần một cái gì đó, như thông tin kết nối DB, thì đó là

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

Tuyến đường / Bộ điều khiển

Tôi muốn để lại các tuyến đường của mình với các bộ điều khiển của mình và sắp xếp chúng trong một app/controllersthư mục con. Sau đó tôi có thể tải chúng lên và để chúng thêm bất kỳ tuyến đường nào chúng cần.

Trong app/server.coffeetập tin coffeescript của tôi, tôi làm:

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

Vì vậy, tôi có các tập tin như:

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

Và ví dụ trong bộ điều khiển miền của tôi, tôi có một setupchức năng như thế này.

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

Lượt xem

Đưa quan điểm vào app/viewsđang trở thành nơi thông thường. Tôi đặt nó ra như thế này.

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

Tệp tĩnh

Đi trong một publicthư mục con.

Github / Semver / NPM

Đặt tệp đánh dấu README.md vào gốc git repo của bạn cho github.

Đặt tệp pack.json với số phiên bản ngữ nghĩa trong gốc git repo của bạn cho NPM.


1
Này Peter! Tôi thực sự thích cách tiếp cận này. Tôi đang làm việc để xây dựng một dự án nhanh và tôi thực sự muốn làm mọi thứ đúng cách hơn là chỉ hack nó và đặt nó xung quanh. Sẽ là tuyệt vời nếu bạn có một repo mẫu trên github và / hoặc một bài đăng blog trên đó.
suVasH .....

4
Repo này có một loạt các mẫu bạn có thể sử dụng làm tài liệu tham khảo: github.com/f Focusaurus / peterlyons.com
Peter Lyons

75
Kịch bản cà phê làm cho điều này trở nên khó đọc: / Bất kỳ cơ hội nào để có được chỉnh sửa vanilla JS? Cảm ơn
to nướng_flakes

1
Cảm ơn câu trả lời này. Tôi chỉ đang cố gắng để bao bọc tâm trí của tôi xung quanh nó. Làm thế nào để bạn truy cập các bộ điều khiển khác bên trong một bộ điều khiển khác (ví dụ: trong chức năng thiết lập như trênapp.put route, api.needId
chmanie

@PeterLyons: này anh bạn, tôi đã thấy mã nguồn của bạn nhưng không biết làm thế nào để thực hiện chế độ xây dựng, tôi đã cài đặt Govà đưa bintệp vào cấu trúc. Làm thế nào để bạn chạy gotập tin đó bin?
user2002495

51

Sau đây là nguyên văn câu trả lời của Peter Lyons, được chuyển cho vanilla JS từ Coffeescript, theo yêu cầu của một số người khác. Câu trả lời của Peter là rất có thể, và bất cứ ai bỏ phiếu cho câu trả lời của tôi cũng nên bỏ phiếu cho anh ấy.


Cấu hình

Những gì bạn đang làm là tốt. Tôi muốn có không gian tên cấu hình của riêng mình được thiết lập trong một config.jstệp cấp cao nhất với một không gian tên lồng nhau như thế này.

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

Điều này là thân thiện để chỉnh sửa sysadmin. Sau đó, khi tôi cần một cái gì đó, như thông tin kết nối DB, thì đó là

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

Tuyến đường / Bộ điều khiển

Tôi muốn để lại các tuyến đường của mình với các bộ điều khiển của mình và sắp xếp chúng trong một app/controllersthư mục con. Sau đó tôi có thể tải chúng lên và để chúng thêm bất kỳ tuyến đường nào chúng cần.

Trong app/server.jstệp javascript của tôi, tôi làm:

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

Vì vậy, tôi có các tập tin như:

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

Và ví dụ trong bộ điều khiển miền của tôi, tôi có một setupchức năng như thế này.

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

Lượt xem

Đưa quan điểm vào app/viewsđang trở thành nơi thông thường. Tôi đặt nó ra như thế này.

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

Tệp tĩnh

Đi trong một publicthư mục con.

Github / Semver / NPM

Đặt tệp đánh dấu README.md vào gốc git repo của bạn cho github.

Đặt tệp pack.json với số phiên bản ngữ nghĩa trong gốc git repo của bạn cho NPM.


43

Câu hỏi của tôi đã được giới thiệu vào tháng 4 năm 2011, nó rất cũ. Trong thời gian này, tôi có thể cải thiện trải nghiệm của mình với Express.js và cách kiến ​​trúc một ứng dụng được viết bằng thư viện này. Vì vậy, tôi chia sẻ ở đây kinh nghiệm của tôi.

Đây là cấu trúc thư mục của tôi:

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

Mục tiêu của app.jstệp là để khởi động ứng dụng expressjs. Nó tải mô-đun cấu hình, mô-đun logger, chờ kết nối cơ sở dữ liệu, ... và chạy máy chủ tốc hành.

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

tuyến đường /

Thư mục tuyến đường có một index.jstập tin. Mục tiêu của nó là giới thiệu một loại phép thuật để tải tất cả các tệp khác trong routes/thư mục. Đây là cách thực hiện:

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

Với mô-đun đó, việc tạo một định nghĩa và thực hiện tuyến mới thực sự dễ dàng. Ví dụ hello.js:

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

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

Mỗi mô-đun tuyến là độc lập .


Bạn có sử dụng một máy phát điện để tạo cấu trúc này không?
Ashish

18

Tôi thích sử dụng một "ứng dụng" toàn cầu, thay vì xuất một chức năng, v.v.


Tôi chọn nhận lời khuyên từ những người sáng tạo :) BTW, bạn có thể vui lòng cung cấp cho chúng tôi một số mã không?
hóa thân vào

đúng rồi. trong ứng dụng này bạn thấy - github.com/visionmedia/sc
Muff

17

Tôi nghĩ rằng đó là một cách tuyệt vời để làm điều đó. Không giới hạn để thể hiện nhưng tôi đã thấy khá nhiều dự án node.js trên github làm điều tương tự. Chúng lấy ra các tham số cấu hình + các mô-đun nhỏ hơn (trong một số trường hợp, mỗi URI) được bao gồm trong các tệp riêng biệt.

Tôi sẽ khuyên bạn nên thực hiện các dự án cụ thể trên github để có ý tưởng. IMO cách bạn đang làm là chính xác.


16

bây giờ là cuối năm 2015 và sau khi phát triển cấu trúc của tôi trong 3 năm và trong các dự án lớn và nhỏ. Phần kết luận?

Không làm một MVC lớn, nhưng tách nó thành các mô-đun

Vì thế...

Tại sao?

  • Thông thường, một hoạt động trên một mô-đun (ví dụ: Sản phẩm) mà bạn có thể thay đổi độc lập.

  • Bạn có thể sử dụng lại các mô-đun

  • Bạn có thể kiểm tra nó một cách riêng biệt

  • Bạn có thể thay thế nó một cách riêng biệt

  • Họ có giao diện rõ ràng (ổn định)

    -Tại mới nhất, nếu có nhiều nhà phát triển làm việc, phân tách mô-đun giúp

Các nodebootstrap dự án có một cách tiếp cận tương tự như cấu trúc cuối cùng của tôi. ( github )

Làm thế nào để cấu trúc này trông như thế nào?

  1. Các mô-đun nhỏ, được bọc , mỗi mô-đun có MVC riêng

  2. Mỗi mô-đun có một gói.json

  3. Kiểm tra như một phần của cấu trúc (trong mỗi mô-đun)

  4. Cấu hình , thư viện và dịch vụ toàn cầu

  5. Docker tích hợp, Cluster, mãi mãi

Thư mục tổng quan (xem thư mục lib cho các mô-đun):

cấu trúc gật gù


3
Sẽ rất hữu ích nếu bạn có thể cập nhật ảnh tổng quan về thư mục với các mô-đun riêng lẻ được mở rộng, như một ví dụ về cách bạn cũng sẽ cấu trúc chúng.
youngrrrr

8

Tôi đang cho cấu trúc thư mục kiểu MVC xin vui lòng tìm dưới đây.

Chúng tôi đã sử dụng cấu trúc thư mục dưới đây cho các ứng dụng web lớn và vừa.

 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

Tôi đã tạo một mô-đun npm để tạo cấu trúc thư mục mvc express.

Vui lòng tìm bên dưới https://www.npmjs.com/package/express-mvc-generator

Chỉ cần các bước đơn giản để tạo và sử dụng mô-đun này.

i) cài đặt mô-đun npm install express-mvc-generator -g

ii) kiểm tra tùy chọn express -h

iii) Tạo cấu trúc mvc express express myapp

iv) Cài đặt phụ thuộc npm install::

v) Mở config / database.js của bạn, Vui lòng định cấu hình db mongo của bạn.

vi) Chạy ứng dụng node apphoặcnodemon app

vii) Kiểm tra URL http: // localhost: 8042 / đăng ký HOẶC http: // yourip: 8042 / đăng ký


7

Đã khá lâu kể từ khi câu trả lời cuối cùng cho câu hỏi này và Express cũng mới phát hành phiên bản 4, đã thêm một vài điều hữu ích để tổ chức cấu trúc ứng dụng của bạn.

Dưới đây là một bài đăng blog cập nhật dài về các thực tiễn tốt nhất về cách cấu trúc ứng dụng Express của bạn. http://www.terlici.com/2014/08/25/best-practices-express-structure.html

Ngoài ra còn có một kho GitHub áp dụng lời khuyên trong bài viết. Nó luôn được cập nhật với phiên bản Express mới nhất.
https://github.com/terlici/base-express


7

Tôi không nghĩ rằng đó là một cách tiếp cận tốt để thêm các tuyến đường để cấu hình. Một cấu trúc tốt hơn có thể là một cái gì đó như thế này:

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

Vì vậy, Products.js và users.js sẽ chứa tất cả các tuyến đường của bạn sẽ có tất cả logic bên trong.


6

Vâng, tôi đặt các tuyến đường của mình dưới dạng tệp json, mà tôi đã đọc lúc đầu và trong một vòng lặp for trong app.js thiết lập các tuyến đường. Route.json bao gồm chế độ xem nào sẽ được gọi và khóa cho các giá trị sẽ được gửi vào tuyến.
Điều này hoạt động cho nhiều trường hợp đơn giản, nhưng tôi đã phải tự tạo một số tuyến đường cho các trường hợp đặc biệt.


6

Tôi đã viết một bài chính xác về vấn đề này. Về cơ bản, nó sử dụng một routeRegistrarlặp đi lặp lại thông qua các tệp trong thư mục /controllersgọi chức năng của nó init. Hàm initlấy appbiến express làm tham số để bạn có thể đăng ký các tuyến theo cách bạn muốn.

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) Hệ thống tập tin dự án Express của bạn có thể như:

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

app.js - kho ứng dụng toàn cầu của bạn

2) Tệp chính mô-đun (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) Kết nối mô-đun trong app.js chính

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

4) Logic mẫu

lib/login
lib/db
lib/config
lib/users
lib/verify
lib/
   /api/ 
   ...
lib/
   /admin/
      /users/
      /settings/
      /groups/
...
  • Tốt nhất để thử nghiệm
  • Tốt nhất cho quy mô
  • Riêng biệt phụ thuộc bởi mô-đun
  • Nhóm tuyến theo chức năng (hoặc mô-đun)

tj nói / hiển thị trên Vimeo ý tưởng thú vị về cách mô đun hóa ứng dụng express - Các ứng dụng web mô-đun với Node.js và Express . Mạnh mẽ và đơn giản.


4

http://locomactorjs.org/ cung cấp một cách để cấu trúc một ứng dụng được xây dựng bằng Node.js và Express.

Từ trang web:

"Đầu máy là một khung web cho Node.js. Đầu máy hỗ trợ các mẫu MVC, các tuyến RESTful và quy ước về cấu hình, đồng thời tích hợp hoàn hảo với bất kỳ cơ sở dữ liệu và công cụ mẫu nào. từ nút. "


3

Gần đây tôi đã chấp nhận các mô-đun như các ứng dụng nhỏ độc lập.

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

Bây giờ đối với bất kỳ định tuyến mô-đun (# .js), chế độ xem (* .ejs), js, css và tài sản nằm cạnh nhau. định tuyến mô hình con được thiết lập trong # .js cha với hai dòng bổ sung

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

Cách này thậm chí sububmodules là có thể.

Đừng quên thiết lập chế độ xem cho thư mục src

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

bất kỳ liên kết nào đến github với cấu trúc như vậy đều quan tâm đến việc xem các tuyến đường, chế độ xem và mô hình đang được tải như thế nào
Muhammad Umer

Tôi nghĩ mọi thứ đều được giải thích. Các tuyến chỉ là các tuyến tốc hành cổ điển. Các khung nhìn cần được tải tiền tố với tên mô-đun, các mô hình cần được tải bằng cách tham chiếu đường dẫn tương đối.
zevero

Trên dòng cuối cùng của tôi, tôi đặt chế độ xem cho thư mục src. Vì vậy, từ đây trở đi, tất cả các khung nhìn đều có thể truy cập được liên quan đến thư mục src. Không có gì lạ mắt.
zevero

1

Đây là cách mà hầu hết cấu trúc thư mục dự án thể hiện của tôi trông.

Tôi thường làm một express dirnamekhởi tạo dự án, tha thứ cho sự lười biếng của tôi, nhưng nó rất linh hoạt và có thể mở rộng. Tái bút - bạn cần có được express-generatorđiều đó (đối với những người đang tìm kiếm nó sudo npm install -g express-generator, sudo vì bạn đang cài đặt nó trên toàn cầu)

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

Bạn phải tự hỏi tại sao tập tin .env? Bởi vì họ làm việc! Tôi sử dụng dotenvmô-đun trong các dự án của tôi (rất nhiều gần đây) và nó hoạt động! Pop trong 2 tuyên bố này trong app.jshoặcwww

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

Và một dòng khác để nhanh chóng được đặt /bower_componentsđể phục vụ nội dung tĩnh trong tài nguyên/ext

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

Nó có thể phù hợp với những người đang muốn sử dụng Express và Angular cùng nhau, hoặc chỉ thể hiện mà không có javascriptshệ thống phân cấp đó .


1

Cấu trúc của tôi thể hiện 4. https://github.com/odirleiborgert/borgert-express-boilerplate

Gói

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

Kết cấu

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

Một cách đơn giản để cấu trúc ứng dụng ur express:

  • Trong index.js chính, thứ tự sau nên được duy trì.

    tất cả app.set nên là đầu tiên.

    tất cả app.use nên là thứ hai.

    theo sau là các apis khác với chức năng hoặc tiếp tục lộ trình trong các tệp khác

    Exapmle

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

    app.use ("/ người dùng", userApi);

    app.post ("/ mã thông báo", Passport.createToken);

    app.post ("/ đăng xuất", Passport.logout)


0

Cách tốt nhất để cấu trúc MVC cho dự án ExpressJs với tay cầm & 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 vui lòng kiểm tra
Manishkumar Bhavnani
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.