惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Spread Privacy
Spread Privacy
K
Kaspersky official blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Forbes - Security
Forbes - Security
Hacker News - Newest:
Hacker News - Newest: "LLM"
The Last Watchdog
The Last Watchdog
SecWiki News
SecWiki News
Attack and Defense Labs
Attack and Defense Labs
Google DeepMind News
Google DeepMind News
Security Archives - TechRepublic
Security Archives - TechRepublic
S
Secure Thoughts
WordPress大学
WordPress大学
Microsoft Security Blog
Microsoft Security Blog
P
Proofpoint News Feed
云风的 BLOG
云风的 BLOG
V
Visual Studio Blog
Security Latest
Security Latest
TaoSecurity Blog
TaoSecurity Blog
Cyberwarzone
Cyberwarzone
S
SegmentFault 最新的问题
Cloudbric
Cloudbric
aimingoo的专栏
aimingoo的专栏
S
Schneier on Security
N
Netflix TechBlog - Medium
MyScale Blog
MyScale Blog
T
The Blog of Author Tim Ferriss
H
Hacker News: Front Page
C
Cybersecurity and Infrastructure Security Agency CISA
小众软件
小众软件
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
AWS News Blog
AWS News Blog
AI
AI
G
GRAHAM CLULEY
IT之家
IT之家
P
Privacy & Cybersecurity Law Blog
L
Lohrmann on Cybersecurity
Last Week in AI
Last Week in AI
D
Docker
Recent Announcements
Recent Announcements
O
OpenAI News
T
Threat Research - Cisco Blogs
GbyAI
GbyAI
S
Security @ Cisco Blogs
T
Troy Hunt's Blog
C
Check Point Blog
博客园 - 三生石上(FineUI控件)
A
About on SuperTechFans
The Cloudflare Blog
阮一峰的网络日志
阮一峰的网络日志
N
News and Events Feed by Topic

Kent C. Dodds Blog

Implementing Hybrid Semantic + Lexical Search Simplifying Containers with Cloudflare Sandboxes Migrating to Workspaces and Nx Offloading FFmpeg with Cloudflare Building Semantic Search on my Content Helping YOU ask ME questions with AI How I used Cursor to Migrate Frameworks The Dow's Start on the Covenant Path 2025 in Review The next chapter: EpicAI.pro AI is taking your job How I increased my visibility Launching Epic Web 2023 in Review Stop Being a Junior RSC with Dan Abramov and Joe Savona Live Stream Fixing a Memory Leak in a Production Node.js App 2022 in Review My Car Accident I Migrated from a Postgres Cluster to Distributed SQLite with LiteFS I'm building EpicWeb.dev A review of my time at Remix Remix: The Yang to React's Yin How I help you build better websites Why I Love Remix The State Initializer Pattern How to React ⚛️ Get a catch block error message with TypeScript Building an awesome image loading experience How Remix makes CSS clashes predictable Introducing the new kentcdodds.com How I built a modern website in 2021 How to use React Context effectively Static vs Unit vs Integration vs E2E Testing for Frontend Apps The Testing Trophy and Testing Classifications Array reduce vs chaining vs for loop Don't Solve Problems, Eliminate Them Super Simple Start to Remix Super Simple Start to ESModules in Node.js JavaScript Pass By Value Function Parameters How to write a Constrained Identity Function (CIF) in TypeScript How to optimize your context value How to write a React Component in TypeScript TypeScript Function Syntaxes Listify a JavaScript Array Build vs Buy: Component Libraries edition Using fetch with TypeScript Wrapping React.useState with TypeScript Define function overload types with TypeScript 2020 in Review Business and Engineering alignment Hi, thanks for reaching out to me 👋 useEffect vs useLayoutEffect Super simple start to Firebase functions Super simple start to Netlify functions Super Simple Start to css variables Favor Progress Over Pride in Open Source Testing Implementation Details How getting into Open Source has been awesome for me useState lazy initialization and function updates Use ternaries rather than && in JSX Application State Management with React Use react-error-boundary to handle errors in React JavaScript to Know for React What open source project should I contribute to? When I follow TDD AHA Programming 💡 How I Record Educational Videos Should I write a test or fix a bug? Stop mocking fetch Intentional Career Building Improve test error messages of your abstractions Tracing user interactions with React Eliminate an entire category of bugs with a few simple tools Common mistakes with React Testing Library Super Simple Start to React Stop using client-side route redirects The State Reducer Pattern with React Hooks Function forms Replace axios with a simple custom fetch wrapper How to test custom React hooks React Production Performance Monitoring Should I useState or useReducer? Stop using isLoading booleans Make Your Test Fail Make your own DevTools An Argument for Automation Fix the "not wrapped in act(...)" warning Super Simple Start to ESModules in the Browser Implementing a simple state machine library in JavaScript 2010s Decade in Review Why users care about how you write code Why I avoid nesting closures Don't call a React function component Why your team needs TestingJavaScript.com Inversion of Control Understanding React's key prop How to Enable React Concurrent Mode How to add testing to an existing project Profile a React App for Performance
How I structure Express apps
2020-07-13 · via Kent C. Dodds Blog

TL;DR–Explore the example repository

This is the typical structure I use for my medium-sized Node backends. For small backends, I'd probably just put everything in one file and I might not bother with build tooling.

package.json

Let's start in the package.json. Here are the relevant bits:

{
	"main": "index.js",
	"engines": {
		"node": "12.18.2"
	},
	"dependencies": {
		"express": "^4.17.1",
		"express-async-errors": "^3.1.1",
		"loglevel": "^1.6.8"
	},
	"devDependencies": {
		"@babel/cli": "^7.10.4",
		"@babel/core": "^7.10.4",
		"@babel/preset-env": "^7.10.4",
		"@babel/register": "^7.10.4",
		"nodemon": "^2.0.4"
	},
	"scripts": {
		"start": "node .",
		"build": "babel --delete-dir-on-start --out-dir dist --copy-files --ignore \"**/__tests__/**,**/__mocks__/**\" --no-copy-ignored src"
	}
}

main

This is the entry for our server. So when we run node . in this directory, this is the file that will be run.

engines

This indicates to tools we use which version of node we intend the project to run with.

dependencies

express is a given (there are plenty of alternatives and if you use one of them that's great, you may still be able to get something out of this blog post regardless). For every Express.js app I have, I also use express-async-errors because it allows me to write my middleware using async/await which is basically a necessity for me. Much less error prone because it ensures that any async errors will be propagated to your error handling middleware.

I like loglevel personally, there are lots of other tools for logging, but loglevel is a good start.

devDependencies

I compile all my stuff with Babel. This allows us to use syntax that's not quite supported in our environment yet (mostly just ESModules) as well as handy plugins like babel-plugin-macros. Hence all the @babel packages:

  • @babel/core is the core babel dependency. Everything else needs it.
  • @babel/cli is for the build script to compile our source code to the output code that Node can run.
  • @babel/preset-env makes it really easy to include all the typical language plugins and transforms we'll need for the environment we're building for.
  • @babel/register is used during development.

If you're using TypeScript, then you may also want to add @babel/preset-typescript.

I also use nodemon for watch mode (restarts the server when files are changed).

scripts

The start script simply runs node . which will run the main file (which we have set to index.js).

The build script takes all of the files in src directory (short for "source") and compiles them with babel to the dist directory (short for distribution). Here's an explanation for all the options:

  • --delete-dir-on-start ensures that we don't have old files hanging around between builds
  • --out-dir dist indicates where we want the compiled version of the files to be saved
  • --copy-files indicates that files that are not compiled should be copied instead (useful for .json files for example)
  • --ignore \"**/__tests__/**,**/__mocks__/**\" is necessary so we don't bother compiling any test-related files because we don't need those in production anyway
  • --no-copy-ignored since we're not compiling the ignored files, we want to indicate that we'd also like to not bother copying them either (so this disables --copy-files for the ignored files).

If you're using TypeScript, make sure to add --extensions ".ts,.tsx,.js" to the build script.

.babelrc.js

Here's what the .babelrc.js looks like:

const pkg = require('./package.json')

module.exports = {
	presets: [
		[
			'@babel/preset-env',
			{
				targets: {
					node: pkg.engines.node,
				},
			},
		],
	],
}

It's pretty simple. We compile all the code down to the version of JavaScript syntax that supported by the engines.node value specified in our package.json.

If we were using TypeScript (recommended for teams), then we'd also include @babel/preset-typescript as well.

index.js

Here's our entry file for the module (this is the main from package.json):

if (process.env.NODE_ENV === 'production') {
	require('./dist')
} else {
	require('nodemon')({ script: 'dev.js' })
}

When we run our app in production, it's running on a server which has been configured to set the NODE_ENV environment variable to 'production'. So with our index.js set up the way it is, in production, it will start the server with the compiled version of our code.

However, when running the project locally, instead we'll require nodemon and pass it the options {script: 'dev.js'} which will tell nodemon to run the dev.js script, and re-run it when we make changes. This will improve our feedback loop as we make changes to the server. There are a lot more options for nodemon, and someone mentioned to me that node-dev is another good project to look into so you might give that a look as well.

dev.js

This one's pretty simple:

require('@babel/register')
require('./src')

The @babel/register sets up babel to compile our files "on the fly" meaning as they're required, Babel will first compile the file before Node gets a chance to run it. Then the require('./src') will require our src/index.js file which is where things really start happening.

src/index.js

This file is pretty simple:

import logger from 'loglevel'
import { startServer } from './start'

logger.setLevel('info')

startServer()

All it does is configure the logger and starts the server. Most projects I've seen actually kick off the server in the src/index.js file, but I prefer to take the logic for starting the server and put it in a function because it makes it easier for testing.

src/start.js

Ok, here's where things really start getting "expressy". For this one, I'll explain things in code comments.

import express from 'express'

// this is all it takes to enable async/await for express middleware
import 'express-async-errors'

import logger from 'loglevel'

// all the routes for my app are retrieved from the src/routes/index.js module
import { getRoutes } from './routes'

function startServer({ port = process.env.PORT } = {}) {
	const app = express()

	// I mount my entire app to the /api route (or you could just do "/" if you want)
	app.use('/api', getRoutes())

	// add the generic error handler just in case errors are missed by middleware
	app.use(errorMiddleware)

	// I prefer dealing with promises. It makes testing easier, among other things.
	// So this block of code allows me to start the express app and resolve the
	// promise with the express server
	return new Promise((resolve) => {
		const server = app.listen(port, () => {
			logger.info(`Listening on port ${server.address().port}`)

			// this block of code turns `server.close` into a promise API
			const originalClose = server.close.bind(server)
			server.close = () => {
				return new Promise((resolveClose) => {
					originalClose(resolveClose)
				})
			}

			// this ensures that we properly close the server when the program exists
			setupCloseOnExit(server)

			// resolve the whole promise with the express server
			resolve(server)
		})
	})
}

// here's our generic error handler for situations where we didn't handle
// errors properly
function errorMiddleware(error, req, res, next) {
	if (res.headersSent) {
		next(error)
	} else {
		logger.error(error)
		res.status(500)
		res.json({
			message: error.message,
			// we only add a `stack` property in non-production environments
			...(process.env.NODE_ENV === 'production'
				? null
				: { stack: error.stack }),
		})
	}
}

// ensures we close the server in the event of an error.
function setupCloseOnExit(server) {
	// thank you stack overflow
	// https://stackoverflow.com/a/14032965/971592
	async function exitHandler(options = {}) {
		await server
			.close()
			.then(() => {
				logger.info('Server successfully closed')
			})
			.catch((e) => {
				logger.warn('Something went wrong closing the server', e.stack)
			})

		if (options.exit) process.exit()
	}

	// do something when app is closing
	process.on('exit', exitHandler)

	// catches ctrl+c event
	process.on('SIGINT', exitHandler.bind(null, { exit: true }))

	// catches "kill pid" (for example: nodemon restart)
	process.on('SIGUSR1', exitHandler.bind(null, { exit: true }))
	process.on('SIGUSR2', exitHandler.bind(null, { exit: true }))

	// catches uncaught exceptions
	process.on('uncaughtException', exitHandler.bind(null, { exit: true }))
}

export { startServer }

Doing things this way makes it easier to test. For example, an integration test could simply do this:

import { startServer } from '../start'

let server, baseURL
beforeAll(async () => {
	server = await startServer()
	baseURL = `http://localhost:${server.address().port}/api`
})

afterAll(() => server.close())

// make requests to the baseURL

If this sounds interesting to you, then let me teach you on TestingJavaScript.com 🏆

src/routes/index.js

This is where all the routes for my app come together:

import express from 'express'
// any other routes imports would go here
import { getMathRoutes } from './math'

function getRoutes() {
	// create a router for all the routes of our app
	const router = express.Router()

	router.use('/math', getMathRoutes())
	// any additional routes would go here

	return router
}

export { getRoutes }

src/routes/math.js

This is just a contrived example of some routes/middleware/express controllers.

import express from 'express'

// A function to get the routes.
// That way all the route definitions are in one place which I like.
// This is the only thing that's exported
function getMathRoutes() {
	const router = express.Router()
	router.get('/add', add)
	router.get('/subtract', subtract)
	return router
}

// all the controller and utility functions here:
async function add(req, res) {
	const sum = Number(req.query.a) + Number(req.query.c)
	res.send(sum.toString())
}

async function subtract(req, res) {
	const difference = Number(req.query.a) - Number(req.query.b)
	res.send(difference.toString())
}

export { getMathRoutes }

Conclusion

And that's it. Hopefully that's interesting and useful! If you want to learn about the testing side of this stuff, don't miss the Test Node.js Backends module on TestingJavaScript.com.