Erstellen einer Unternehmens-App mit Node / Express


71

Ich versuche zu verstehen, wie Unternehmensanwendungen mit Node / Express / Mongo strukturiert werden (tatsächlich mit MEAN-Stack).

Nachdem ich 2 Bücher gelesen und gegoogelt hatte (einschließlich ähnlicher StackOverflow-Fragen), konnte ich kein gutes Beispiel für die Strukturierung großer Anwendungen mit Express finden. Alle Quellen, die ich gelesen habe, schlagen vor, die Anwendung nach folgenden Entitäten aufzuteilen:

  • Routen
  • Steuerungen
  • Modelle

Aber das Hauptproblem ich mit dieser Struktur sehen ist , dass Controller wie Gott Objekte sind, sie kennt req, resObjekte, verantwortlich für die Validierung und hat Business - Logik enthält in.

Auf der anderen Seite scheinen mir Routen ein Über-Engineering zu sein, da sie lediglich Endpunkte (Pfade) Controller-Methoden zuordnen.

Ich habe Scala / Java-Hintergrund, daher habe ich die Gewohnheit, die gesamte Logik in drei Ebenen zu trennen - Controller / Service / Dao.

Für mich sind folgende Aussagen ideal:

  • Die Controller sind nur für die Interaktion mit dem WEB-Teil verantwortlich, dh für das Marshalling / Unmarshalling, eine einfache Validierung (erforderlich, min, max, E-Mail-Regex usw.).

  • Die Service-Schicht (die ich in NodeJS / Express-Apps tatsächlich vermisst habe) ist nur für die Geschäftslogik verantwortlich, einige Geschäftsvalidierungen. Die Serviceschicht weiß nichts über den WEB-Teil (dh sie können von einem anderen Anwendungsort aus aufgerufen werden, nicht nur aus dem Webkontext).

  • In Bezug auf die DAO-Schicht ist mir alles klar. Mungomodelle sind eigentlich DAO, daher ist es mir hier am klarsten.

Ich denke, Beispiele, die ich gesehen habe, sind sehr einfach und zeigen nur Konzepte von Node / Express, aber ich möchte ein Beispiel aus der Praxis betrachten, bei dem ein Großteil der Geschäftslogik / -validierung eine Rolle spielt.

BEARBEITEN:

Eine andere Sache, die mir nicht klar ist, ist das Fehlen von DTO-Objekten. Betrachten Sie dieses Beispiel:

const mongoose = require('mongoose');
const Article = mongoose.model('Article');
exports.create = function(req, res) {
    // Create a new article object
    const article = new Article(req.body);
    // saving article and other code
}

Dort wird das JSON-Objekt von req.bodyals Parameter zum Erstellen des Mongo-Dokuments übergeben. Es riecht schlecht für mich. Ich würde gerne mit konkreten Klassen arbeiten, nicht mit rohem JSON

Vielen Dank.


1
Vielleicht möchten Sie diese Frage überprüfen: stackoverflow.com/questions/5178334/… Und dieses Wesentliche: gist.github.com/lancejpollard/1398757
Rocketspacer

Sie können es strukturieren, wie Sie es für richtig halten. Anstatt einen Artikel direkt unter dem Controller zu erstellen, können Sie ihn jederzeit in eine andere Ebene in einem anderen Verzeichnis, z. B.
Service.createArticle, trennen und

Das req.body-Objekt sollte in ein Javascript-Objekt konvertiert worden sein, wenn Sie Express-Middleware wie Body-Parser verwendet haben. Und ja, auch wenn es kein roher JSON mehr ist, übergeben wir ihn nicht direkt an den DTO-Konstruktor. Es muss eine Validierung und Desinfektion bestehen
Rocketspacer

3
Es gibt kein Richtig oder Falsch bei der Strukturierung eines Projekts. Unterschiedliche Unternehmensprojekte haben unterschiedliche Strukturen, weil sie unterschiedliche Aufgaben ausführen. Einige müssen ein Echtzeit-Framework wie socket.io unterstützen, andere benötigen einen Hintergrunddämon, um Nachrichtenwarteschlangen zu nutzen, und viele andere Dinge. Strukturieren Sie Ihre App einfach so, wie Sie es kennen. Allmählich ändert sich die Struktur, wenn Ihre App skaliert
Rocketspacer

Antworten:


135

Controller sind    Gottobjekte, bis Sie nicht möchten, dass sie so sind ...
- Sie sagen nicht zurfyx (╯ ° □ °)))

Nur an der Lösung interessiert? Springe zum neuesten Abschnitt "Ergebnis" .

┬──┬◡ ノ (° - ° ノ)

Bevor ich mit der Antwort beginne, möchte ich mich dafür entschuldigen, dass diese Antwort viel länger als die übliche SO-Länge ist. Controller alleine machen nichts, es geht nur um das gesamte MVC-Muster. Daher hielt ich es für wichtig, alle wichtigen Details zum Router <-> Controller <-> Service <-> Modell durchzugehen, um Ihnen zu zeigen, wie Sie geeignete isolierte Controller mit minimalen Verantwortlichkeiten erreichen.

Hypothetischer Fall

Beginnen wir mit einem kleinen hypothetischen Fall:

  • Ich möchte eine API haben, die eine Benutzersuche über AJAX ermöglicht.
  • Ich möchte eine API haben, die auch die gleiche Benutzersuche über Socket.io ermöglicht.

Beginnen wir mit Express. Das ist einfach peasy, nicht wahr?

route.js

import * as userControllers from 'controllers/users';
router.get('/users/:username', userControllers.getUser);

controller / user.js

import User from '../models/User';
function getUser(req, res, next) {
  const username = req.params.username;
  if (username === '') {
    return res.status(500).json({ error: 'Username can\'t be blank' });
  }
  try {
    const user = await User.find({ username }).exec();
    return res.status(200).json(user);
  } catch (error) {
    return res.status(500).json(error);
  }
}

Jetzt machen wir den Socket.io-Teil:

Da dies keine socket.io- Frage ist, überspringe ich das Boilerplate.

import User from '../models/User';
socket.on('RequestUser', (data, ack) => {
  const username = data.username;
  if (username === '') {
    ack ({ error: 'Username can\'t be blank' });
  }
  try {
    const user = User.find({ username }).exec();
    return ack(user);
  } catch (error) {
    return ack(error);
  }
});

Ähm, hier riecht etwas ...

  • if (username === ''). Wir mussten den Controller-Validator zweimal schreiben. Was wäre, wenn es nController-Validatoren gäbe ? Müssten wir zwei (oder mehr) Kopien von jeder auf dem neuesten Stand halten?
  • User.find({ username })wird zweimal wiederholt. Das könnte möglicherweise eine Dienstleistung sein.

Wir haben gerade zwei Controller geschrieben, die den genauen Definitionen von Express und Socket.io zugeordnet sind. Sie werden höchstwahrscheinlich während ihres Lebens niemals kaputt gehen, da sowohl Express als auch Socket.io in der Regel abwärtskompatibel sind. ABER sie sind nicht wiederverwendbar. Express für Hapi wechseln ? Sie müssen alle Ihre Controller wiederholen.

Ein weiterer schlechter Geruch, der vielleicht nicht so offensichtlich ist ...

Die Controller-Antwort ist handgefertigt. .json({ error: whatever })

APIs in RL ändern sich ständig. In Zukunft möchten Sie vielleicht, dass Ihre Antwort { err: whatever }oder etwas Komplexeres (und Nützlicheres) wie:{ error: whatever, status: 500 }

Fangen wir an (eine mögliche Lösung)

Ich kann es nicht die Lösung nennen, weil es unendlich viele Lösungen gibt. Es liegt an Ihrer Kreativität und Ihren Bedürfnissen. Das Folgende ist eine anständige Lösung; Ich verwende es in einem relativ großen Projekt und es scheint gut zu funktionieren, und es behebt alles, worauf ich zuvor hingewiesen habe.

Ich gehe zu Modell -> Service -> Controller -> Router, um es bis zum Ende interessant zu halten.

Modell

Ich werde nicht auf Details des Modells eingehen, da dies nicht Gegenstand der Frage ist.

Sie sollten eine ähnliche Mungo-Modellstruktur wie die folgende haben:

models / User / validate.js

export function validateUsername(username) {
  return true;
}

Weitere Informationen zur geeigneten Struktur für Mungo 4.x-Validatoren finden Sie hier .

models / User / index.js

import { validateUsername } from './validate';

const userSchema = new Schema({
  username: { 
    type: String, 
    unique: true,
    validate: [{ validator: validateUsername, msg: 'Invalid username' }],
  },
}, { timestamps: true });

const User = mongoose.model('User', userSchema);

export default User;

Nur ein einfaches Benutzerschema mit einem Benutzernamenfeld und created updatedmungogesteuerten Feldern.

Der Grund, warum ich das validateFeld hier eingefügt habe, ist, dass Sie feststellen, dass Sie die meisten Modellvalidierungen hier und nicht im Controller durchführen sollten.

Das Mungo-Schema ist der letzte Schritt vor dem Erreichen der Datenbank. Wenn jemand MongoDB nicht direkt abfragt, können Sie immer sicher sein, dass jeder Ihre Modellvalidierungen durchläuft, was Ihnen mehr Sicherheit bietet, als sie auf Ihrem Controller zu platzieren. Nicht zu sagen, dass Unit-Test-Validatoren wie im vorherigen Beispiel trivial sind.

Lesen Sie hier und hier mehr darüber .

Bedienung

Der Dienst fungiert als Prozessor. Bei akzeptablen Parametern werden diese verarbeitet und ein Wert zurückgegeben.

In den meisten Fällen (einschließlich dieses) werden Mongoose-Modelle verwendet und ein Versprechen zurückgegeben (oder ein Rückruf; aber ich würde ES6 definitiv mit Versprechen verwenden, wenn Sie dies nicht bereits tun).

services / user.js

function getUser(username) {
  return User.find({ username}).exec(); // Just as a mongoose reminder, .exec() on find 
                               // returns a Promise instead of the standard callback.
}

An diesem Punkt wundern Sie sich vielleicht, kein catchBlock? Nein, weil wir später einen coolen Trick machen werden und für diesen Fall keinen benutzerdefinierten brauchen.

In anderen Fällen reicht ein einfacher Synchronisierungsdienst aus. Stellen Sie sicher, dass Ihr Synchronisierungsdienst niemals E / A enthält, da Sie sonst den gesamten Node.js-Thread blockieren .

services / user.js

function isChucknorris(username) {
  return ['Chuck Norris', 'Jon Skeet'].indexOf(username) !== -1;
}

Regler

Wir möchten doppelte Controller vermeiden, daher haben wir nur einen Controller für jede Aktion.

controller / user.js

export function getUser(username) {
}

Wie sieht diese Signatur jetzt aus? Schön, oder? Da wir nur am Parameter username interessiert sind, müssen wir keine nutzlosen Dinge wie nehmen req, res, next.

Fügen wir die fehlenden Validatoren und den fehlenden Service hinzu:

controller / user.js

import { getUser as getUserService } from '../services/user.js'

function getUser(username) {
  if (username === '') {
    throw new Error('Username can\'t be blank');
  }
  return getUserService(username);
}

Sieht immer noch ordentlich aus, aber ... was ist mit dem throw new Error, bringt das meine Anwendung nicht zum Absturz? - Shh, warte. Wir sind noch nicht fertig.

An diesem Punkt würde unsere Controller-Dokumentation also ungefähr so ​​aussehen:

/**
 * Get a user by username.
 * @param username a string value that represents user's username.
 * @returns A Promise, an exception or a value.
 */

Was ist der "Wert" in der angegeben @returns? Denken Sie daran, dass wir vorhin gesagt haben, dass unsere Dienste sowohl synchron als auch asynchron sein können (mit Promise)? getUserServiceist in diesem Fall asynchron, der isChucknorrisDienst jedoch nicht. Daher wird einfach ein Wert anstelle eines Versprechens zurückgegeben.

Hoffentlich wird jeder die Dokumente lesen. Weil sie einige Controller anders behandeln müssen als andere, und einige von ihnen benötigen einen try-catchBlock.

Da wir Entwicklern (einschließlich mir) nicht vertrauen können, dass sie die Dokumente lesen, bevor sie es zuerst versuchen, müssen wir an dieser Stelle eine Entscheidung treffen:

  • Controller, um eine PromiseRückgabe zu erzwingen
  • Service, um immer ein Versprechen zurückzugeben

⬑ Dies löst die inkonsistente Controller-Rückgabe (nicht die Tatsache, dass wir unseren Try-Catch-Block weglassen können).

IMO, ich bevorzuge die erste Option. Weil Controller meistens die meisten Versprechen verketten.

return findUserByUsername
         .then((user) => getChat(user))
         .then((chat) => doSomethingElse(chat))

Wenn wir ES6 Promise verwenden, können wir alternativ eine nette Eigenschaft nutzen, Promiseum dies zu tun: Wir können PromiseNicht-Versprechen während ihrer Lebensdauer verarbeiten und trotzdem immer wieder Folgendes zurückgeben Promise:

return promise
         .then(() => nonPromise)
         .then(() => // I can keep on with a Promise.

Wenn der einzige Dienst, den wir anrufen, nicht verwendet wird Promise, können wir selbst einen erstellen.

return Promise.resolve() // Initialize Promise for the first time.
  .then(() => isChucknorris('someone'));

Wenn wir zu unserem Beispiel zurückkehren, würde dies Folgendes ergeben:

...
return Promise.resolve()
  .then(() => getUserService(username));

Wir brauchen Promise.resolve()in diesem Fall nicht wirklich, da getUserServicebereits ein Versprechen zurückgegeben wird, aber wir wollen konsistent sein.

Wenn Sie sich über den catchBlock wundern : Wir möchten ihn nicht in unserem Controller verwenden, es sei denn, wir möchten eine benutzerdefinierte Behandlung durchführen. Auf diese Weise können wir die beiden bereits integrierten Kommunikationskanäle (Ausnahme für Fehler und Rückgabe für Erfolgsmeldungen) nutzen, um unsere Nachrichten über einzelne Kanäle zu übermitteln.

Anstelle von ES6 Promise .thenkönnen wir das neuere ES2017 async / await( jetzt offiziell ) in unseren Controllern verwenden:

async function myController() {
    const user = await findUserByUsername();
    const chat = await getChat(user);
    const somethingElse = doSomethingElse(chat);
    return somethingElse;
}

Beachten Sie asyncvor dem function.

Router

Endlich der Router, yay!

Wir haben dem Benutzer also noch nichts geantwortet. Wir haben lediglich einen Controller, von dem wir wissen, dass er IMMER einen Promise(hoffentlich mit Daten) zurückgibt . Oh!, Und das kann möglicherweise eine Ausnahme auslösen, wenn throw new Error is calledoder einige Service- PromisePausen.

Der Router wird denjenigen, der sie in einer einheitlichen Art und Weise, Steuer Petitionen und Rückgabedaten zu Kunden, einige vorhandenen Daten sein, nulloder undefined dataoder ein Fehler.

Der Router ist der EINZIGE, der mehrere Definitionen hat. Die Anzahl hängt von unseren Abfangjägern ab. Im hypothetischen Fall waren dies API (mit Express) und Socket (mit Socket.io).

Lassen Sie uns überprüfen, was wir tun müssen:

Wir möchten, dass unser Router in konvertiert (req, res, next)wird (username). Eine naive Version wäre ungefähr so:

router.get('users/:username', (req, res, next) => {
  try {
    const result = await getUser(req.params.username); // Remember: getUser is the controller.
    return res.status(200).json(result);
  } catch (error) {
    return res.status(500).json(error);
  }
});

Obwohl es gut funktionieren würde, würde dies zu einer enormen Menge an Codeduplizierungen führen, wenn wir dieses Snippet auf allen unseren Routen kopieren und einfügen würden. Wir müssen also eine bessere Abstraktion machen.

In diesem Fall können wir eine Art gefälschten Router-Client erstellen, der ein Versprechen und nParameter einhält und dessen Routing und returnAufgaben genau wie auf jeder der Routen ausführt.

/**
 * Handles controller execution and responds to user (API Express version).
 * Web socket has a similar handler implementation.
 * @param promise Controller Promise. I.e. getUser.
 * @param params A function (req, res, next), all of which are optional
 * that maps our desired controller parameters. I.e. (req) => [req.params.username, ...].
 */
const controllerHandler = (promise, params) => async (req, res, next) => {
  const boundParams = params ? params(req, res, next) : [];
  try {
    const result = await promise(...boundParams);
    return res.json(result || { message: 'OK' });
  } catch (error) {
    return res.status(500).json(error);
  }
};
const c = controllerHandler; // Just a name shortener.

Wenn Sie mehr über diesen Trick erfahren möchten , können Sie die Vollversion in meiner anderen Antwort in React-Redux und Websockets mit socket.io (Abschnitt "SocketClient.js") nachlesen.

Wie würde Ihre Route mit dem aussehen controllerHandler?

router.get('users/:username', c(getUser, (req, res, next) => [req.params.username]));

Eine saubere Zeile, genau wie am Anfang.

Weitere optionale Schritte

Controller verspricht

Dies gilt nur für diejenigen, die ES6-Versprechen verwenden. Die ES2017- async / awaitVersion sieht für mich bereits gut aus.

Aus irgendeinem Grund mag ich es nicht, Promise.resolve()Namen verwenden zu müssen, um das Initialisierungsversprechen zu erstellen. Es ist einfach nicht klar, was dort los ist.

Ich möchte sie lieber durch etwas Verständlicheres ersetzen:

const chain = Promise.resolve(); // Write this as an external imported variable or a global.

chain
  .then(() => ...)
  .then(() => ...)

Jetzt wissen Sie, dass dies chainden Beginn einer Kette von Versprechen markiert. Jeder, der Ihren Code liest, oder wenn nicht, geht er zumindest davon aus, dass es sich um eine Kette handelt, die ein Service funktioniert.

Express-Fehlerbehandlungsroutine

Express verfügt über eine Standardfehlerbehandlungsroutine, mit der Sie mindestens die unerwartetsten Fehler erfassen sollten.

router.use((err, req, res, next) => {
  // Expected errors always throw Error.
  // Unexpected errors will either throw unexpected stuff or crash the application.
  if (Object.prototype.isPrototypeOf.call(Error.prototype, err)) {
    return res.status(err.status || 500).json({ error: err.message });
  }

  console.error('~~~ Unexpected error exception start ~~~');
  console.error(req);
  console.error(err);
  console.error('~~~ Unexpected error exception end ~~~');


  return res.status(500).json({ error: '⁽ƈ ͡ (ुŏ̥̥̥̥םŏ̥̥̥̥) ु' });
});

Was mehr ist, sollten Sie wahrscheinlich etwas wie Debug oder Winston anstelle von verwenden console.error, die professionellere Methoden zum Umgang mit Protokollen sind.

Und so stecken wir das in controllerHandler:

  ...
  } catch (error) {
    return res.status(500) && next(error);
  }

Wir leiten jeden erfassten Fehler einfach an den Fehlerbehandler von Express weiter.

Fehler als ApiError

Errorwird als Standardklasse zum Einkapseln von Fehlern beim Auslösen einer Ausnahme in Javascript angesehen. Wenn Sie wirklich nur Ihre eigenen kontrollierten Fehler verfolgen möchten, würde ich wahrscheinlich den throw Errorund den Express-Fehlerhandler von Errorauf ändern ApiError, und Sie können ihn sogar besser an Ihre Bedürfnisse anpassen, indem Sie ihm das Statusfeld hinzufügen.

export class ApiError {
  constructor(message, status = 500) {
    this.message = message;
    this.status = status;
  }
}

Zusätzliche Information

Benutzerdefinierte Ausnahmen

Sie können jede benutzerdefinierte Ausnahme jederzeit mit throw new Error('whatever')oder mithilfe von auslösen new Promise((resolve, reject) => reject('whatever')). Du musst nur damit spielen Promise.

ES6 ES2017

Das ist ein sehr einfühlsamer Punkt. IMO ES6 (oder sogar ES2017 , das jetzt über offizielle Funktionen verfügt) ist die geeignete Methode, um an großen Projekten zu arbeiten, die auf Node basieren.

Wenn Sie es noch nicht verwenden, schauen Sie sich die ES6- Funktionen sowie den ES2017- und Babel- Transpiler an.

Ergebnis

Dies ist nur der vollständige Code (bereits zuvor gezeigt) ohne Kommentare oder Anmerkungen. Sie können alles in Bezug auf diesen Code überprüfen, indem Sie zum entsprechenden Abschnitt scrollen.

router.js

const controllerHandler = (promise, params) => async (req, res, next) => {
  const boundParams = params ? params(req, res, next) : [];
  try {
    const result = await promise(...boundParams);
    return res.json(result || { message: 'OK' });
  } catch (error) {
    return res.status(500) && next(error);
  }
};
const c = controllerHandler;

router.get('/users/:username', c(getUser, (req, res, next) => [req.params.username]));

controller / user.js

import { serviceFunction } from service/user.js
export async function getUser(username) {
  const user = await findUserByUsername();
  const chat = await getChat(user);
  const somethingElse = doSomethingElse(chat);
  return somethingElse;
}

services / user.js

import User from '../models/User';
export function getUser(username) {
  return User.find({}).exec();
}

models / User / index.js

import { validateUsername } from './validate';

const userSchema = new Schema({
  username: { 
    type: String, 
    unique: true,
    validate: [{ validator: validateUsername, msg: 'Invalid username' }],
  },
}, { timestamps: true });

const User = mongoose.model('User', userSchema);

export default User;

models / User / validate.js

export function validateUsername(username) {
  return true;
}

1
Ich mochte deinen controllerHandlerTrick. Aber können Sie mir bitte einen Vorschlag machen, wenn ich eine Ansicht mit der Template-Engine rendern möchte? Ich meine res.render('index', {title: 'Hello'}). Wie sollten Controller und Route in diesem Fall aussehen?
MyTitle

1
@MyTitle Ich würde es vermeiden, Controller zu modifizieren. {title: 'Hello'}können die aktuellen Objektwerte sein, die bereits an die gesendet wurden handleController. 'index' sollte wahrscheinlich ein zusätzlicher Parameter für die handleControllerFunktion sein, da kein Controller an der Ansicht interessiert ist, die das Ergebnis rendern wird. Abhängig von Ihren Anforderungen möchten Sie möglicherweise zwei oder mehr Ansichten übergeben, um verschiedene Arten von Ergebnissen oder Fehlern zu behandeln. Zunächst würde ich jedoch einfach einen Dateinamen für die einzige Ansicht übergeben und dann einen Standardfehler im "Express Error Handler" haben "(wo es derzeit ein ist res.json).
Zurfyx

Wie kann ich eine App-Instanz in Controllern erhalten? Ich muss einige Ereignisse mitapp.emit('some event')
Amarjit Singh

Ordentliche controllerHandlerImplementierung
Donovan Keating

6

Jeder hat seine eigene Art, das Projekt in bestimmte Ordner zu unterteilen. Die Struktur, die ich benutze, ist

  • config
  • Protokolle
  • Routen
  • Steuerungen
  • Modelle
  • Dienstleistungen
  • Utensilien
  • app.js / server.js / index.js (jeder Name, den du bevorzugst)

Der Konfigurationsordner enthält Konfigurationsdateien wie Datenbankverbindungseinstellungen für alle Entwicklungsphasen wie "Produktion", "Entwicklung", "Testen".

Beispiel

'use strict'
var dbsettings = {
    "production": {
//your test settings
    },
    "test": {

    },
    "development": {
        "database": "be",
        "username": "yourname",
        "password": "yourpassword",
        "host": "localhost",
        "connectionLimit": 100
    }
}
module.exports = dbsettings

Der Protokollordner enthält Ihre Verbindungsprotokolle, Fehlerprotokolle zum Debuggen

Der Controller dient zur Validierung Ihrer Anforderungsdaten und Ihrer Geschäftslogik

Beispiel

const service = require("../../service")
const async = require("async")
exports.techverify = (data, callback) => {

    async.series([
        (cb) => {
            let searchObject = { accessToken: data.accessToken }
            service.admin.get(searchObject, (err, result) => {
                if (err || result.length == 0) {
                    callback(err, { message: "accessToken is invalid" })
                } else {
                    delete data.accessToken
                    service.tech.update(data, { verified: true }, (err, affe, res) => {
                        if (!err)
                            callback(err, { message: "verification done" })
                        else
                            callback(err, { message: "error occured" })
                    })
                }
            })
        }
    ])
}

Modelle dient zum Definieren Ihres Datenbankschemas

Beispiel mongoDb Schema

'use strict'
let mongoose = require('mongoose');
let schema = mongoose.Schema;
let user = new schema({
    accesstoken: { type: String },
    firstname: { type: String },
    lastname: { type: String },
    email: { type: String, unique: true },
    image: { type: String },
    phoneNo: { type: String },
    gender: { type: String },
    deviceType: { type: String },
    password: { type: String },
    regAddress: { type: String },
    pincode: { type: String },
    fbId: { type: String, default: 0 },
    created_at: { type: Date, default: Date.now },
    updated_at: { type: Date, default: Date.now },
    one_time_password: { type: String },
    forgot_password_token: { type: String },
    is_block: { type: Boolean, default: 0 },
    skin_type: { type: String },
    hair_length: { type: String },
    hair_type: { type: String },
    credits: { type: Number, default: 0 },
    invite_code: { type: String },
    refered_by: { type: String },
    card_details: [{
        card_type: { type: String },
        card_no: { type: String },
        card_cv_no: { type: String },
        created_at: { type: Date }
    }]
});
module.exports = mongoose.model('user', user);

Dienste dienen zum Schreiben Ihrer Datenbankabfrage. Vermeiden Sie das Schreiben von Abfragen im Controller. Versuchen Sie, eine Abfrage in diesen Ordner zu schreiben, und rufen Sie sie im Controller auf

Abfragen mit Mungo

'use strict'
const modelUser = require('../../models/user');
exports.insert = (data, callback) => {
    console.log('mongo log for insert function', data)
    new modelUser(data).save(callback)
}
exports.get = (data, callback) => {
    console.log('mongo log for get function', data)
    modelUser.find(data, callback)
}
exports.update = (data, updateData, callback) => {
    console.log('mongo log for update function', data)
    modelUser.update(data, updateData, callback);
}
exports.getWithProjection = (data, projection, callback) => {
    console.log('mongo log for get function', data)
    modelUser.find(data, projection, callback)
}

utils ist für allgemeine Dienstprogrammfunktionen gedacht, die in Ihrem Projekt häufig verwendet werden, z. B. zum Verschlüsseln, Entschlüsseln von Kennwörtern usw.

Beispiel

exports.checkPassword = (text, psypherText) => {
    console.log("checkPassword executed")
    console.log(text, psypherText)
    return bcrypt.compareSync(text, psypherText)
}
exports.generateToken = (userEmail) => {
    return jwt.sign({ unique: userEmail, timeStamp: Date.now }, config.keys.jsonwebtoken)
}

0

Die Antwort von rohit salaria erklärt im Grunde die gleiche App-Struktur, die Sie in Java gewohnt sind.

  • Controller sind die Controller in Java
  • Modelle sind die Datenzugriffsschicht
  • Services sind die Service-Schicht

Ich habe jedoch ein paar Bemerkungen. Das erste und wichtigste ist, dass dies nicht Java ist. Es mag offensichtlich klingen, aber schauen Sie sich einfach Ihre Frage an und sehen Sie, dass Sie dieselbe Entwicklungserfahrung mit denselben Konzepten suchen, die Sie in der Java-Welt verwendet haben. Meine folgenden Bemerkungen sind nur die Erklärung dafür.

Fehlende DTOs. In Java sind sie nur erforderlich, Punkt. In einer Java-Webanwendung, in der Sie Ihre Daten in einer relationalen Datenbank speichern und Daten an das Front-End in JSON senden und empfangen, konvertieren Sie die Daten natürlich in ein Java-Objekt. In einer Node-App ist jedoch alles Javascript und JSON. Das ist eine der Stärken der Plattform. Da JSON das übliche Datenformat ist, ist es nicht erforderlich, Code zu schreiben oder von Bibliotheken abhängig zu sein, um zwischen dem Datenformat Ihrer Ebenen zu übersetzen.

Übergeben des Datenobjekts direkt von der Anforderung an das Modell. Warum nicht? Wenn Sie JSON als gemeinsames Datenformat vom Front-End bis zur Datenbank verwenden, können Sie das Datenmodell Ihrer App auf einfache Weise zwischen all Ihren Ebenen synchronisieren. Natürlich müssen Sie diesen Weg nicht gehen, aber er reicht meistens aus. Warum also nicht? Die Validierung erfolgt im Modell, wo es gemäß der MVC-Theorie gehört (und nicht im Controller, wo Faulheit und Pragmatismus es oft ausdrücken :)).

Für den letzten Gedanken möchte ich hinzufügen, dass dies nicht die beste Plattform ist, wenn es um die Skalierung der Projektgröße geht. Es ist überhaupt Nod Bat, aber Java ist in dieser Hinsicht besser.


0

einfache und grundlegende Regel

  1. Halten Sie die zugehörigen Komponenten nahe beieinander.

  2. Teilen Sie die Seite in Komponenten und arbeiten Sie

  3. Alle abhängigen Komponenten sollten zusammen sein

  4. Gemeinsame Dinge sollten unabhängig von allen anderen Komponenten gehalten werden.

Schließlich ist jede Sprache süß. Es ist nur so, wie vertraut Sie mit der Sprache sind. Sie können den Kampf nur gewinnen, wenn Sie mit Ihrem Schwert vertraut sind.

Ich entwickle Angular2-Anwendung mit NodeJS, Angular2 Ich werde Ihnen mit meiner Verzeichnisstruktur helfen.

Hauptmodul

`das Hauptmodul` 

Submodul

 `die Submodulstruktur`

gemeinsames Modul

`Behalten Sie den freigegebenen Ordner als separates Modul bei`

Ich hoffe es hilft :)


Für was brauchen wir einen freigegebenen Ordner?
Menai Ala Eddine - Aladdin

die Komponente, Dienste, Richtlinien, die im gesamten Projekt gemeinsam genutzt werden und gemeinsam sind.
Manish Kumar
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.