Hoe u een productieklare Node- en Express-app schrijft

Project structureren

Toen ik begon met het bouwen van Node & Express-applicaties, wist ik niet hoe belangrijk het was om je applicatie te structureren. Express heeft geen strikte regels of richtlijnen voor het onderhouden van de projectstructuur.

U bent vrij om elke gewenste structuur te gebruiken. Wanneer je codebase groeit, heb je uiteindelijk lange routehandlers. Dit maakt uw code moeilijk te begrijpen en bevat mogelijke bugs.

Als je voor een startup werkt, heb je meestal geen tijd om je project te refractoreren of te modulariseren. U kunt eindigen met een eindeloze reeks bugfixes en patches.

In de loop van de tijd, terwijl ik met zowel kleine teams als grote teams werkte, realiseerde ik me wat voor soort structuur kan meegroeien met je project en toch gemakkelijk te onderhouden is.

Model View Controller

Het MVC-patroon helpt bij een snelle en parallelle ontwikkeling. De ene ontwikkelaar kan bijvoorbeeld aan de weergave werken, terwijl een andere kan werken aan het creëren van de bedrijfslogica in de controller.

Laten we eens kijken naar een voorbeeld van een eenvoudige CRUD-applicatie voor gebruikers.

project/ controllers/ users.js util/ plugin.js middlewares/ auth.js models/ user.js routes/ user.js router.js public/ js/ css/ img/ views/ users/ index.jade tests/ users/ create-user-test.js update-user-test.js get-user-test.js .gitignore app.js package.json
  • controllers: definieer de route-handlers en bedrijfslogica van uw app
  • util: schrijft hier hulpprogramma- / helperfuncties die door alle controllers kunnen worden gebruikt. U kunt bijvoorbeeld een functie schrijven zoals mergeTwoArrays(arr1, arr2).
  • middlewares: U kunt middlewares schrijven om alle inkomende verzoeken te interpreteren voordat u naar de route-handler gaat. Bijvoorbeeld,

    router.post('/login', auth, controller.login)waar authis een middleware-functie gedefinieerd in middlewares/auth.js.

  • modellen: ook een soort middleware tussen je controller en de database. U kunt een schema definiëren en een aantal validaties uitvoeren voordat u naar de database schrijft. U kunt bijvoorbeeld een ORM zoals Mongoose gebruiken, die wordt geleverd met geweldige functies en methoden om in het schema zelf te gebruiken
  • routes: definieer uw app-routes met HTTP-methoden. U kunt bijvoorbeeld alles definiëren met betrekking tot de gebruiker.
router.post('/users/create', controller.create) router.put('/users/:userId', controller.update) router.get('/users', controller.getAll)
  • openbaar: bewaar statische afbeeldingen in /img, aangepaste JavaScript-bestanden en CSS/css
  • views: Bevat sjablonen die door de server moeten worden weergegeven.
  • tests: Hier kunt u alle unit tests of acceptatietests voor de API-server schrijven.
  • app.js: fungeert als het hoofdbestand van het project waarin u de app en andere elementen van het project initialiseert.
  • package.json: zorgt voor de afhankelijkheden, de scripts die met de npmopdracht moeten worden uitgevoerd en de versie van uw project.

Uitzonderingen en foutafhandeling

Dit is een van de belangrijkste aspecten om over na te denken bij het maken van een project met elke taal. Laten we eens kijken hoe we fouten en uitzonderingen netjes kunnen afhandelen in een Express-app.

Beloftes gebruiken

Een van de voordelen van het gebruik van beloften ten opzichte van callbacks is dat ze impliciete of expliciete uitzonderingen / fouten in asynchrone codeblokken kunnen verwerken, evenals voor synchrone code gedefinieerd in .then(), een belofte callback

Voeg gewoon toe .catch(next)aan het einde van de belofteketen. Bijvoorbeeld:

router.post('/create', (req, res, next) => { User.create(req.body) // function to store user data in db .then(result => { // do something with result return result }) .then(user => res.json(user)) .catch(next) })

Met behulp van try-catch

Try-catch is een traditionele manier om uitzonderingen op te vangen in asynchrone code.

Laten we een voorbeeld bekijken met de mogelijkheid om een ​​uitzondering te krijgen:

router.get('/search', (req, res) => { setImmediate(() => { const jsonStr = req.query.params try { const jsonObj = JSON.parse(jsonStr) res.send('Success') } catch (e) { res.status(400).send('Invalid JSON string') } }) })

Gebruik geen synchrone code

Synchrone code, ook wel blokkeringscode genoemd, omdat deze de uitvoering blokkeert totdat ze worden uitgevoerd.

Gebruik dus geen synchrone functies of methoden die milliseconden of microseconden kunnen duren. Voor een website met veel verkeer zal het toenemen en kan dit leiden tot een hoge latentie of reactietijd van de API-verzoeken.

Gebruik ze vooral niet in de productie :)

Veel Node.js modules worden geleverd met zowel .syncen .asyncmethoden, dus gebruik async in de productie.

Maar als u toch een synchrone API wilt gebruiken, gebruik dan de --trace-sync-ioopdrachtregelvlag. Het zal een waarschuwing en een stacktracering afdrukken wanneer uw toepassing een synchrone API gebruikt.

Zie voor meer informatie over de basisprincipes van foutafhandeling:

  • Foutafhandeling in Node.js
  • Robuuste knooppuntapplicaties bouwen: foutafhandeling (StrongLoop-blog)
Wat u niet moet doen, is naar de uncaughtExceptiongebeurtenis luisteren , die wordt uitgezonden wanneer een uitzondering helemaal teruggaat naar de gebeurtenislus. Het heeft over het algemeen niet de voorkeur.

Correct loggen

Logboekregistratie is essentieel voor foutopsporing en app-activiteit. Het wordt voornamelijk gebruikt voor ontwikkelingsdoeleinden. We gebruiken console.logen console.errormaar dit zijn synchrone functies.

Voor foutopsporingsdoeleinden

U kunt een module zoals debug gebruiken. Met deze module kunt u de omgevingsvariabele DEBUG gebruiken om te bepalen naar welke foutopsporingsberichten console.err(), indien aanwezig, worden verzonden .

Voor app-activiteit

Een manier is om ze naar de database te schrijven.

Bekijk hoe ik mongoose-plug-ins heb gebruikt om mijn applicatie te controleren.

Another way is to write to a file OR use a logging library like Winston or Bunyan. For a detailed comparison of these two libraries, see the StrongLoop blog post Comparing Winston and Bunyan Node.js Logging.

require(“./../../../../../../”) mess

There are different workarounds for this problem.

If you find any module getting popular and if it has logical independence from the application, you can convert it to private npm module and use it like any other module in package.json.

OR

const path = require('path'); const HOMEDIR = path.join(__dirname,'..','..');

where __dirname is the built-in variable that names the directory that contains the current file, and .. ,..is the requisite number of steps up the directory tree to reach the root of the project.

From there it is simply:

const foo = require(path.join(HOMEDIR,'lib','foo')); const bar = require(path.join(HOMEDIR,'lib','foo','bar'));

to load an arbitrary file within the project.

Let me know in the comment below if you have better ideas :)

Set NODE_ENV to “production”

The NODE_ENV environment variable specifies the environment in which an application is running (usually, development or production). One of the simplest things you can do to improve performance is to set NODE_ENVto “production.”

Setting NODE_ENV to “production” makes Express:

  • Cache view templates.
  • Cache CSS files generated from CSS extensions.
  • Generate less verbose error messages.

Tests indicate that just doing this can improve app performance by a factor of three!

Using Process Manager

For production, you should not simply use node app.j — if your app crashes, it will be offline until you restart it.

The most popular process managers for Node are:

  • StrongLoop Process Manager
  • PM2
  • Forever

I personally use PM2.

For a feature-by-feature comparison of the three process managers, see //strong-pm.io/compare/. For a more detailed introduction to all three, see Process managers for Express apps.

Run your app in a cluster

In a multi-core system, you can increase the performance of a Node app by many times by launching a cluster of processes.

A cluster runs multiple instances of the app, ideally one instance on each CPU core. This distributes the load and tasks among the instances.

Using Node’s cluster module

Clustering is made possible with Node’s cluster module. This enables a master process to spawn worker processes. It distributes incoming connections among the workers.

However, rather than using this module directly, it’s far better to use one of the many tools out there that do it for you automatically. For example node-pm or cluster-service.

Using PM2

For pm2 you can use cluster directly through a command. For example,

# Start 4 worker processes pm2 start app.js -i 4 # Auto-detect number of available CPUs and start that many worker processes pm2 start app.js -i max 

If you encounter any problems, feel free to get in touch or comment below.

I would be happy to help :)

Don’t hesitate to clap if you considered this a worthwhile read!

References: //expressjs.com/en/advanced/best-practice-performance.html

Originally published at 101node.io on September 30, 2018.