route handler와 middleware가 실행되는 동안 발생하는 모든 오류를 Express가 잡아서 처리할 수 있도록 확인하는 것이 중요하다.
route handler
Express에서 route handlers는 어플리케이션의 특정 경로(또는 엔드포인트)에 대해 수신되는 HTTP 요청을 처리하는 함수이다.
이는 어플리케이션의 라우팅 구성에 정의되어 있으며, 클라이언트에게 응답을 보내거나 요청을 다음 middleware 함수에 전달하는 책임이 있다.
Route handlers는 수신되는 요청과 응답 객체를 매개변수로 받아, 이 객체를 조작하여 클라이언트에 응답을 보낼 수 있고,
개발자가 어플리케이션의 다른 경로에 대한 특정 동작을 정의할 수 있게 해주어, 동적이고 유연한 API와 웹 애플리케이션을 구축할 수 있게 한다.const express = require('express'); const app = express(); app.get('/', (req, res) => { res.send('Hello World!'); }); app.listen(3000, () => { console.log('Example app listening on port 3000!'); }); // app.get('/', (req, res) => { ... }) 는 '/' 경로로의 수신되는 GET 요청을 듣는 route handler. // 요청이 수신되면 핸들러 함수 (req, res) => { ... } 가 실행 // 이 함수는 "Hello World!" 라는 메시지와 함께 클라이언트에 응답을 보냄.
route handlers와 middleware 내부의 동기적 코드에서 발생하는 오류는 추가적인 작업 없이도 처리되고, 동기적 코드가 Error를 발생시키면, Express가 이를 잡아서 처리한다.
app.get('/', (req, res) => {
throw new Error('BROKEN') // Express가 Error를 잡아서 처리한다.
})
app.get('/', (req, res, next) => {
fs.readFile('/file-does-not-exist', (err, data) => {
if (err) {
next(err) // Express로 Error전달
} else {
res.send(data)
}
})
})
app.get('/user/:id', async (req, res, next) => {
const user = await getUserById(req.params.id)
res.send(user)
})
next()
함수에 getUserById가 Error를 던지거나 거부하면, 던진 Error나 거부된 값으로 next가 호출.
거부된 값이 제공되지 않으면, Express 라우터가 제공하는 기본 Error 객체로 next가 호출.
next()
함수에 'route' 문자열을 제외한 다른 것을 전달하면, Express는 현재 요청이 Error로 간주되고, 나머지 Error 처리 라우팅 및 middleware 기능은 건너뛴다.
콜백이 sequence에서 데이터를 제공하지 않고 오직 Error만 제공하는 경우, 코드를 다음과 같이 간소화할 수 있습니다.
sequence
여러 개의 middleware 함수 또는 route handler가 순서대로 실행되는 것을 말함
요청이 들어오면 각 middleware 함수 또는 route handler가 순차적으로 실행되어 응답을 제공
각 단계에서 다음 middleware 함수 또는 route handler로 요청이 전달되면서 처리가 완료
app.get('/', [
function (req, res, next) {
fs.writeFile('/inaccessible-path', 'data', next)
},
function (req, res) {
res.send('OK')
}
])
fs.writeFile
의 콜백에 next가 제공됨.app.get('/', (req, res, next) => {
setTimeout(() => {
try {
throw new Error('BROKEN')
} catch (err) {
next(err)
}
}, 100)
})
app.get('/', (req, res, next) => {
Promise.resolve().then(() => {
throw new Error('BROKEN')
}).catch(next) // Error는 Express에 전달
})
app.get('/', [
function (req, res, next) {
fs.readFile('/maybe-valid-file', 'utf-8', (err, data) => {
res.locals.data = data
next(err)
})
},
function (req, res) {
res.locals.data = res.locals.data.split(',')[1]
res.send(res.locals.data)
}
])
readFile
호출로부터 두 개의 간단한 문장. readFile
이 error를 일으키면, Express에게 error를 전달하고, 그렇지 않으면 다음 핸들러로 빠르게 돌아가서 동기 에러 프로세싱 영역으로 돌아간다.readFile
의 콜백 내부에서 이 프로세싱을 했다면, 애플리케이션이 종료될 수 있으며 Express 에러 핸들러들이 실행되지 않을 수 있다.next()
를 호출하여 오류를 전달하면, 사용자 정의 오류 핸들러에서 처리하지 않은 경우 내장 오류 핸들러가 처리한다.res.statusCode
는 err.status
(또는 err.statusCode
)로부터 설정. 이 값이 4xx 또는 5xx 범위 외의 값일 경우, 500으로 설정됨.res.statusMessage
는 상태 코드에 따라 설정됨.err.stack
.만약 응답을 스트리밍하는 도중에 에러가 발생한다면(예를 들어 클라이언트로의 응답 스트리밍 중에 에러가 발생할 경우),
next() 함수를 호출하여 Express 기본 에러 핸들러가 연결을 끊고 요청을 실패시킨다.
그래서 사용자 정의 에러 핸들러를 추가할 때, 헤더가 클라이언트에 이미 전송되었을 때 기본 Express 에러 핸들러로 위임해야함.
function errorHandler (err, req, res, next) {
if (res.headersSent) {
return next(err)
}
res.status(500)
res.render('error', { error: err })
}
app.use((err, req, res, next) => {
console.error(err.stack)
res.status(500).send('Something broke!')
})
const bodyParser = require('body-parser')
const methodOverride = require('method-override')
app.use(bodyParser.urlencoded({
extended: true
}))
app.use(bodyParser.json())
app.use(methodOverride())
app.use((err, req, res, next) => {
// logic
})
const bodyParser = require('body-parser')
const methodOverride = require('method-override')
app.use(bodyParser.urlencoded({
extended: true
}))
app.use(bodyParser.json())
app.use(methodOverride())
app.use(logErrors)
app.use(clientErrorHandler)
app.use(errorHandler)
function logErrors (err, req, res, next) {
console.error(err.stack)
next(err)
}
function clientErrorHandler (err, req, res, next) {
if (req.xhr) {
res.status(500).send({ error: 'Something failed!' })
} else {
next(err)
}
}
function errorHandler (err, req, res, next) {
res.status(500)
res.render('error', { error: err })
}
app.get('/a_route_behind_paywall',
(req, res, next) => {
if (!req.user.hasPaid) {
// continue handling this request
next('route')
} else {
next()
}
}, (req, res, next) => {
PaidContent.find((err, doc) => {
if (err) return next(err)
res.json(doc)
})
})
https://blog.siner.io/2020/01/04/express-middleware/
https://velog.io/@younoah/nodejs-express-error
Express에서 Error Handling은 애플리케이션에서 발생하는 오류를 처리하는 것을 말합니다. 이는 적절한 오류 메시지를 클라이언트에 전송하거나, 애플리케이션의 로깅 정보에 기록하는 것 등을 포함할 수 있습니다. Express에서는 미들웨어 함수를 사용하여 오류 핸들링을 수행할 수 있습니다.
Express에서 Error Handling은 애플리케이션의 에러처리를 위한 방법입니다. 에러 핸들링을 위한 미들웨어를 정의하고, 에러가 발생하면 기본적으로 정의된 에러 핸들링 미들웨어를 호출하는 것이 일반적입니다. 만약 커스텀 에러 핸들링 미들웨어를 정의하면 기본 에러 핸들링 미들웨어 대신 커스텀 에러 핸들링 미들웨어를 호출할 수 있습니다.
Express에서 Error Handling은 어플리케이션에서 발생하는 에러를 처리하기 위한 기능입니다. Error Handling을 정의하는 방법은 아래와 같습니다.
1. 에러 핸들링 미들웨어 정의: Error Handling을 위한 미들웨어를 정의할 수 있습니다. Error Handling 미들웨어는 일반 미들웨어와 다르게 4개의 인자(err, req, res, next)를 갖습니다.
2. 에러 핸들링 미들웨어 호출: app.use() 호출 뒤에 에러 핸들링 미들웨어를 정의하며, next() 함수를 통해 에러 핸들링 미들웨어를 호출할 수 있습니다.
3. 에러 메시지 작성: Error Handling 미들웨어에서 res.write() 혹은 res.send() 등의 메시지를 작성하여, 에러 메시지를 전달할 수 있습니다.
4. 응답 종료: Error Handling 미들웨어에서 next() 함수를 호출하지 않은 경우, res.end() 를 호출하여 응답을 종료해야 합니다. 그렇지 않으면, 요청이 "막히" 되어 가비지 컬렉션
Express에서 커스텀 에러 핸들링 미들웨어는 개발자가 정의하고 구현한 에러 처리 로직을 가진 미들웨어입니다. 이는 애플리케이션에서 발생하는 에러를 처리하는데 사용되며, 애플리케이션의 기본 에러 핸들링 기능을 확장하거나 대체할 수 있습니다.
Express에서 커스텀 에러 핸들링 미들웨어를 정의하는 방법은 다음과 같습니다:
1. 에러 객체를 전달할 수 있는 함수를 작성합니다. 이 함수는 에러 객체를 처리하고 원하는 형식으로 응답을 보냅니다.
2. 이 함수를 app.use() 메서드에 추가합니다.
3. 응용 프로그램 로직에서 에러가 발생하면, next(err) 메서드를 호출하여 에러 객체를 전달합니다.
Express에서 커스텀 에러 핸들링 미들웨어의 예시
app.use(function(err, req, res, next) {
console.error(err.stack);
res.status(500).send('Something broke!');
});
app.get('/a_route', function(req, res, next) {
try {
// some code
} catch (err) {
next(err);
}
});
app.use('/a_route', function(err, req, res, next) {
console.error(err.stack);
res.status(500).send('Something broke!');
});
app.get('/async_route', async function(req, res, next) {
try {
// some asynchronous code
} catch (err) {
next(err);
}
});
app.use(function(err, req, res, next) {
console.error(err.stack);
res.status(500).send('Something broke!');
});
app.use((req, res, next) => {
try {
// Your code here
next();
} catch (error) {
next(error);
}
});
app.use((error, req, res, next) => {
// Your error handling logic here
res.status(500).json({ message: error.message });
});
class CustomError extends Error {
constructor(message) {
super(message);
this.statusCode = 400;
}
}
app.use((error, req, res, next) => {
const statusCode = error.statusCode || 500;
res.status(statusCode).json({ message: error.message });
});
// In your route handlers
if (error) {
throw new CustomError('Invalid request data');
}
app.use(express.static(path.join(__dirname, 'public')));
app.use((req, res, next) => {
const error = new Error('File not found');
error.status = 404;
next(error);
});
app.use((error, req, res, next) => {
res.status(error.status || 500);
res.send(error.message);
});
app.use((req, res, next) => {
if (!req.body.username) {
const error = new Error('Username is required');
error.status = 400;
next(error);
}
});
app.use((error, req, res, next) => {
res.status(error.status || 500);
res.send(error.message);
});
const express = require('express');
const app = express();
app.use((req, res, next) => {
const error = new Error('Not Found');
error.status = 404;
next(error);
});
app.use((error, req, res, next) => {
if (error.status === 404) {
res.status(404).send('Not Found');
} else {
next(error);
}
});
const express = require('express');
const app = express();
app.use((req, res, next) => {
const error = new Error('Not Found');
error.status = 404;
next(error);
});
app.use((error, req, res, next) => {
res.status(error.status || 500);
if (process.env.NODE_ENV === 'development') {
res.send({
error: {
message: error.message,
status: error.status,
stack: error.stack
}
});
} else {
res.send({
error: {
message: error.message,
status: error.status
}
});
}
});
const express = require('express');
const app = express();
const logger = require('morgan');
app.use(logger('dev'));
app.use((req, res, next) => {
const error = new Error('Not Found');
error.status = 404;
next(error);
});
app.use((error, req, res, next) => {
res.status(error.status || 500);
res.send({
error: {
message: error.message,
status: error.status
}
});
});
app.use(function (err, req, res, next) {
console.error(err.message);
res.status(500).send('Something went wrong');
});
이 예시에서, 이전의 미들웨어에서 next에 에러가 전달되면, 이 에러 핸들링 미들웨어에서 이 에러를 받아서 콘솔에 에러 메시지를 기록합니다. 그런 다음 500 상태 코드를 가진 응답이 보내지며, 내부 서버 오류가 발생했음을 의미합니다.
const express = require('express');
const app = express();
// Middleware 1: Parses request data
app.use(express.json());
// Middleware 2: Adds a header to the response
app.use((req, res, next) => {
res.header('X-Middleware', '2');
next();
});
// Middleware 3: Logs the request method
app.use((req, res, next) => {
console.log(`Request method: ${req.method}`);
next();
});
// Route handler
app.get('/', (req, res) => {
res.send('Hello, World!');
});
app.listen(3000, () => {
console.log('Example app listening on port 3000!');
});