The goals
๐ช๐ปDynamic Routes
โ๐ปCustom Middleware & Error Handling
๐๐ปOptimizing Our Code
Why we need Dynamic Routes
we can't make routes with speculation that how many data would be generated.
In this case, we use dynamic routes so it can generate itself while user adding data
res.render()
If we pass two parameters in render, it means that we can use the second item in the first file and usually we call it as a key
relativa path vs absolute path
when we use link, if we don't use '/' then it's a relative path. But if we use '/' in front of the path, then it's absolute path.
To prevent some errors in the future after modifying sturucture, it is recommended that we use abolute path with '/'.
Especially when we separate 'head' itself as an another file and share or connect to other file so we don't have to repeat, in this case we should write the path absolutely not to make an error.
req.params
It takes keys or properties form the path we define.
So, it should be the same as we set on our path. Here, we set id, so it should be params.id
UUId (Uniform Unique ID)
When URL has a specific 'id' of item and we want to show the detail of that specific item
app.get('/restaurants/:id', function (req, res) {
const restaurantId = req.params.id
const filePath = path.join(__dirname, 'data', 'restaurants.json');
const fileData = fs.readFileSync(filePath);
const storedRestaurants = JSON.parse(fileData);
res.render('restaurant-detail', {rid: restaurantId})
});
firstly, bring the data from json file and jsonify
app.get('/restaurants/:id', function (req, res) {
const restaurantId = req.params.id
const filePath = path.join(__dirname, 'data', 'restaurants.json');
const fileData = fs.readFileSync(filePath);
const storedRestaurants = JSON.parse(fileData);
for (const restaurant of storedRestaurants) {
if (restaurant.id === restaurantId) {
res.render('restaurant-detail', {rid: restaurantId})
}
}
});
and use for loop to check if the id of the data is the same to the data we wanna show.
And we used 'for of' loop since it's an array.
app.get('/restaurants/:id', function (req, res) {
const restaurantId = req.params.id
const filePath = path.join(__dirname, 'data', 'restaurants.json');
const fileData = fs.readFileSync(filePath);
const storedRestaurants = JSON.parse(fileData);
for (const restaurant of storedRestaurants) {
if (restaurant.id === restaurantId) {
return res.render('restaurant-detail', {rid: restaurantId})
}
}
});
After we found the data, we should finish this funtion. So we return the value to end the execution of this code.
But, here when we render we set restaurant as a key and give the data from array, const restaurant.
Then where this key restaurant from?
This is from 'restaurant-detail' file. If this key is changed, then we need to change this for loop key also.
We should put render out of for loop, since we need to render '404' after checking all the item in storedRestaurant
app.get('/restaurants/:id', function (req, res) {
const restaurantId = req.params.id
const filePath = path.join(__dirname, 'data', 'restaurants.json');
const fileData = fs.readFileSync(filePath);
const storedRestaurants = JSON.parse(fileData);
for (const restaurant of storedRestaurants) {
if (restaurant.id === restaurantId) {
return res.render('restaurant-detail', {
restaurant: restaurant
})
}
}
res.render('404');
});
How we show error page when user input the wrong path?
It's impossible to make every possible cases as paths.
So we need to write code for all the general cases.
In this case, we can just write code for the general case and put it at the last.
So our computer will read our code from the head and when sithe the cases by the code we wrote, and left over which is not in any cases we wrote, will use this error code.
And there's no filter so it will apply for all the left over.
How to show error page for server error page
500 error code means that something went wrong on the server.
But we already put error handling code for general case, so we need more something special.
So here, we will give 4 parameters to the function and it's telling to express that it's special code and it will work everywhere if there are some cases.
Error Handling
Error Handling refers to how Express catches and processes errors that occur both synchronously and asynchronously. Express comes with a default error handler so you don’t need to write your own to get started.
Catching Errors
It’s important to ensure that Express catches all errors that occur while running route handlers and middleware.
Errors that occur in synchronous code inside route handlers and middleware require no extra work. If synchronous code throws an error, then Express will catch and process it. For example:
app.get('/', (req, res) => {
throw new Error('BROKEN') // Express will catch this on its own.
})
For errors returned from asynchronous functions invoked by route handlers and middleware, you must pass them to the next() function, where Express will catch and process them. For example:
app.get('/', (req, res, next) => {
fs.readFile('/file-does-not-exist', (err, data) => {
if (err) {
next(err) // Pass errors to Express.
} else {
res.send(data)
}
})
})
Starting with Express 5, route handlers and middleware that return a Promise will call next(value) automatically when they reject or throw an error. For example:
app.get('/user/:id', async (req, res, next) => {
const user = await getUserById(req.params.id)
res.send(user)
})
If getUserById throws an error or rejects, next will be called with either the thrown error or the rejected value. If no rejected value is provided, next will be called with a default Error object provided by the Express router.
If you pass anything to the next() function (except the string 'route'), Express regards the current request as being an error and will skip any remaining non-error handling routing and middleware functions.
How to use 'next' method?
Status codes
We can set our code specifically what status codes we wanna give, but sometimes
We are showing the error page but status code says it's okay so we need to fix this status code.
app.get('/restaurants/:id', function (req, res) {
const restaurantId = req.params.id
const filePath = path.join(__dirname, 'data', 'restaurants.json');
const fileData = fs.readFileSync(filePath);
const storedRestaurants = JSON.parse(fileData);
for (const restaurant of storedRestaurants) {
if (restaurant.id === restaurantId) {
return res.render('restaurant-detail', {
restaurant: restaurant
})
}
}
res.status(404).render('404');
});
Method chaining
app.use(function (req, res) {
res.status.render('404');
})
connect the methods and use together
Status simply returns an updated response object.
So, here, technically we are still calling render on our response object, but on a slightly updated one.
Code refactoring
Refactoring means that "rewriting parts of the code" / "restructuring code" - always with the intention of keeping the individual code snippets short, concise and maintainable.
How can we use module we made in other page
module.exports = {
getStoredRestaurants: getStoredRestaurants
}
left side is the key name we wanna use in other files, and right side is the module we wanna export. So we can't change this left sife name.
How to change the path to parent path
const filePath = path.join(__dirname, '..' ,'data', 'restaurants.json');
we put ".." it means that we will move to the parent directory from where we are now.
Express Router
If we wanna separate the basic code in app file and move some common code to other files, then how should we do?
const express = require('express');
app.get('/', function (req, res) {
res.render('index');
});
app.get('/about', function (req, res) {
res.render('about')
});
We brought some codes from our 'app' file cause it's basic code.
But it's based on 'app' but we can't declare here again. Because it's only for our real 'app' file.
So, in this case, we can use Express Router method.
const express = require('express');
const router = express.Router();
router.get('/', function (req, res) {
res.render('index');
});
router.get('/about', function (req, res) {
res.render('about')
});
module.exports = router;
So, we changed 'app' to 'router'.
And we should import it to use in app file then we can use like this:
const resData = require('./util/restaurant-data');
const defaultRoutes = require('./routes/default');
const app = express();
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(express.static('public'));
app.use(express.urlencoded({
extended: false
}));
app.use('/', defaultRoutes);
This will be EVERY request. Every request starts with "/" since every request is at least targeting <your-domain>/something (even if it's just <your-domain>/).
When we use this, if we set it as 'app.use('/restaurants', defaultRoutes), then in this case, in default file all the routes work as '/restaurants/~' so we don't use like this.
Query Parameters
Array.prototype.sort()
The sort() method sorts the elements of an array in place and returns the reference to the same array, now sorted. The default sort order is ascending, built upon converting the elements into strings, then comparing their sequences of UTF-16 code units values.
The time and space complexity of the sort cannot be guaranteed as it depends on the implementation.
Syntax
// Functionless
sort()
// Arrow function
sort((a, b) => { /* … */ } )
// Compare function
sort(compareFn)
// Inline compare function
sort(function compareFn(a, b) { /* … */ })
Parameters
compareFn Optional
Specifies a function that defines the sort order. If omitted, the array elements are converted to strings, then sorted according to each character's Unicode code point value.
a
The first element for comparison.
b
The second element for comparison.
In JavaScript, we can use the ">" and "<" operators not just on numbers but also on strings!
E.g. "Tornado" > "Weather" yield "true"
router.get('/restaurants', function (req, res) {
const storedRestaurants = resData.getStoredRestaurants();
storedRestaurants.sort(function(resA, resB) {
if (resA.name > resB.name) {
return 1
}
return -1
}
);
If we define button in <form> but there's no other items then?
It will automatically submit the data
<form action="/restaurants" method="GET">
<input type="hidden" value="asc" name="order">
<button class="btn">Change Order</button>
</form>
But if we just submit this form, there would be no data. So we put 'input' tag there and set type as 'hidden'.
So, user would not be able to input some data but we can set as developers.
So here we set 'value' as 'asc' which means ascending.
So we will reload this page with some slight change.
In the Server-side code, we can access to the query parameter value, and change the sorting based on the query parameter value
router.get('/restaurants', function (req, res) {
let order = req.query.order
if (order !== 'asc' & order !== 'desc') {
order = 'asc';
}
We can use this code to change our sorting code.
<form action="/restaurants" method="GET">
<input type="hidden" value="<%= nextOrder %>" name="order">
<button class="btn">Change Order</button>
</form>
Since we fix our code like this, every time when we click the button, it changes the data would be submitted by the form.
Query parameter vs Route parameter in Dynamic routes
"๋์ ๋ผ์ฐํธ"์ ๋ผ์ฐํธ ๋งค๊ฐ๋ณ์
์ด ์น์ ์์๋ ๋ผ์ฐํธ ๋งค๊ฐ๋ณ์๋ฅผ ํ์ฉํ์ฌ URL ๊ฒฝ๋ก์์ ๋์ ๊ฐ์ ์ถ์ถํ๋ "๋์ ๋ผ์ฐํธ"์ ๋ํด ๋ฐฐ์ ์ต๋๋ค.
์๋ฅผ ๋ค์ด:
router.get('/restaurants/:id', ...)
์ด ์์์ id๋ ์ด URL/๊ฒฝ๋ก๋ฅผ ๋ฐฉ๋ฌธํ ๋ ๋ค๋ฅธ ๊ฐ์ ์ ์งํ๋ ๋ผ์ฐํธ ๋งค๊ฐ๋ณ์์ ๋๋ค. ์๋ฅผ ๋ค์ด, ์ฌ์ฉ์๊ฐ /restaurants/r1์ ์ ๋ ฅํ๋ฉด id๋ r1๊ณผ ๊ฐ์ต๋๋ค.
๋ผ์ฐํธ ๋งค๊ฐ๋ณ์๋ ํ๋์ ๋จ์ผ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํ์ฌ ๊ตฌ์ฒด์ ์ธ ๋ผ์ฐํธ ๋งค๊ฐ๋ณ์ ๊ฐ์ ๋ฐ๋ผ ๋ค๋ฅธ ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํ ์ ์์ผ๋ฏ๋ก ๋งค์ฐ ์ ์ฉํฉ๋๋ค.
๋ผ์ฐํธ ๋งค๊ฐ๋ณ์(์์ ์์์:id)๊ฐ URL ๊ฒฝ๋ก์ ํต์ฌ ๋ถ๋ถ์์ ์ดํดํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. ์๋ฅผ ๋ค์ด ์์ ๊ฒฝ๋ก์์๋ /restaurants๋ง ์ฒ๋ฆฌ๋์ง ์์ต๋๋ค. ๋ผ์ฐํธ ๋งค๊ฐ๋ณ์์ ๋ํ ๊ฐ์ด ์๊ธฐ ๋๋ฌธ์ ๋๋ค.
์ฟผ๋ฆฌ ๋งค๊ฐ๋ณ์
๋ํ ์ด ์น์ ์์ "์ฟผ๋ฆฌ ๋งค๊ฐ๋ณ์"๋ผ๋ ๊ฐ๋ ์ ๋ํด์๋ ๋ฐฐ์ ์ต๋๋ค.
"์ฟผ๋ฆฌ ๋งค๊ฐ๋ณ์"์ "๋ผ์ฐํธ ๋งค๊ฐ๋ณ์"๋ฅผ ํผ๋ํ๊ธฐ ์ฝ์ง๋ง ์ด๋ค์ ์๋ก ๋ค๋ฅธ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ๊ทผ๋ณธ์ ์ผ๋ก ๋ค๋ฅธ ๊ฐ๋ ์ ๋๋ค.
"๋ผ์ฐํธ ๋งค๊ฐ๋ณ์"(์ ์ฐธ์กฐ)๊ฐ ์ ํ ์ฌํญ์ด ์๋๊ณ ๋ผ์ฐํธ ์ ์์ ํต์ฌ ๋ถ๋ถ์ธ ๊ฒฝ์ฐ(์ฆ, ๋ผ์ฐํธ ๋งค๊ฐ๋ณ์์ ๊ฐ์ด ์ ๊ณต๋์ง ์์ผ๋ฉด ๋ก๋๊ฐ ํ์ฑํ๋์ง ์์) "์ฟผ๋ฆฌ ๋งค๊ฐ๋ณ์"๋ ์์ ํ ์ ํ ์ฌํญ์ ๋๋ค!
"์ฟผ๋ฆฌ ๋งค๊ฐ๋ณ์"๋ ์ผ๋ฐ์ ์ผ๋ก URL ๊ฒฝ๋ก์ ์ถ๊ฐ ์ ๋ณด๋ฅผ ์ถ๊ฐํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
์๋ฅผ ๋ค์ด:
/restaurants?order=asc
์ฌ๊ธฐ์ "order" ์ฟผ๋ฆฌ ๋งค๊ฐ๋ณ์๋ "asc" ๊ฐ์ผ๋ก ์ถ๊ฐ๋ฉ๋๋ค(์ฟผ๋ฆฌ ๋งค๊ฐ๋ณ์๋ ํญ์ ?๊ธฐํธ๋ฅผ ํตํด "์ฃผ ๊ฒฝ๋ก"์ ๋ถ๋ฆฌ๋จ).
๊ธฐ๋ณธ์ ์ผ๋ก ์ฟผ๋ฆฌ ๋งค๊ฐ๋ณ์๋ ์๋ฌด ์์ ๋ ์ํํ์ง ์์ต๋๋ค!
์น์ฌ์ดํธ ๋ฐฉ๋ฌธ์๋ ์ํ๋ ๋งํผ ์ถ๊ฐํ ์ ์์ต๋๋ค. ๋งํฌ๋ฅผ ํตํด ์ถ๊ฐํ๊ฑฐ๋ ์ด์ ๊ฐ์์์์ ๊ฐ์ด GET ์์ฒญ์ ํตํด ์์์ ์ ์ถํ์ฌ ์ถ๊ฐํ ์ ์์ต๋๋ค(์ฐธ๊ณ : GET ์์ ๋์ ์ด์ ๊ฐ์์์ ์ฟผ๋ฆฌ ๋งค๊ฐ๋ณ์๋ก ๋งํฌ๋ฅผ ๊ตฌ์ฑํ ์๋ ์์์ต๋๋ค. - ํ์ง๋ง ์จ๊ฒจ์ง ์ ๋ ฅ ํ๋์ ๋ํ ์์ด๋์ด๋ฅผ ์๊ฐํ๊ณ ์ถ์์ด์.)
์๋ฒ ์ธก์์ ์ถ๊ฐ ์์ ์ ์ํํ๊ธฐ ์ํด ์ด๋ฌํ ์ ํ์ ์ฟผ๋ฆฌ ๋งค๊ฐ๋ณ์๋ฅผ ์ฒ๋ฆฌํ ์ ์๋ ์ฝ๋๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ ๊ฐ๋ฐ์์๊ฒ ๋ฌ๋ ค ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ๋ฐฐ์ด์ ํญ๋ชฉ ์์๋ฅผ ๋ณ๊ฒฝํ ์ ์์ต๋๋ค(๋ง์ง๋ง ๊ฐ์) ํญ๋ชฉ์ ํํฐ๋งํ๊ฑฐ๋ ๋ค๋ฅธ ์์ ์ ์ํํ ์ ์์ต๋๋ค.
๋ผ์ฐํธ ๋งค๊ฐ๋ณ์์ ์ฟผ๋ฆฌ ๋งค๊ฐ๋ณ์
์์์ ์ค๋ช ํ ๊ฒ์ฒ๋ผ ์ฟผ๋ฆฌ ๋งค๊ฐ๋ณ์๋ ์ ํ ์ฌํญ์ด๋ฉฐ ๊ฒฝ๋ก ๋ผ์ฐํธ์ ํต์ฌ ๋ถ๋ถ์ด ์๋์ง๋ง ๋ผ์ฐํธ ๋งค๊ฐ๋ณ์๋ ํ์์ฌํญ์ ๋๋ค!
๋ผ์ฐํธ ๋งค๊ฐ๋ณ์๋ ํ์ฑํ๋์ด์ผ ํ๋ ๊ฒฝ๋ก๋ฅผ ๊ฒฐ์ ํ๋ ๋ฐ ์ฌ์ฉ๋๋ฉฐ ๋ผ์ฐํธ ๋งค๊ฐ๋ณ์์ ๋ํ ๊ตฌ์ฒด์ ์ธ ๊ฐ(์: ์๋น ID)์ ์ป๊ธฐ ์ํด ๋ผ์ฐํธ ๋ด๋ถ์์ ๊ตฌ๋ฌธ ๋ถ์๋ ์ ์์ต๋๋ค.
๋ฐ๋ฉด์ ์ฟผ๋ฆฌ ๋งค๊ฐ๋ณ์๋ ์ ํ ์ฌํญ์ด๊ณ ์ผ๋ฐ์ ์ผ๋ก ์คํํ ๋ผ์ฐํธ๋ฅผ ๊ฒฐ์ ํ๋ ๋ฐ ์ฌ์ฉํ์ง ์์ต๋๋ค. ๋์ ๋ก๋๋ ๋ผ์ฐํธ/URL์ ์ถ๊ฐ ์ ๋ณด๋ฅผ ์ฒจ๋ถํ๋ ๋ฐ ์ฌ์ฉํ ์ ์์ต๋๋ค.
Sources
https://yohanpro.com/posts/nodejs/error-handling
(NodeJS) Express ์๋ฌ ํธ๋ค๋งํ๊ธฐ - Yohan's Developer Diary
์๋ฌ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ ์ด๋ป๊ฒ ๊ณตํต๋ก์ง์ผ๋ก ์ฒ๋ฆฌ๋ฅผ ํด์ฃผ์ด์ผ ํ๋๊ฐ? express๋ฅผ ์ด์ฉํ๋ฉด ๋ฏธ๋ค์จ์ด๋ฅผ app.use()๋ฅผ ์ฌ์ฉํ์ฌ ์ฝ๊ฒ ๋ฑ๋กํ ์ ์๋ค. ์ด๋ฅผ ์ด์ฉํ๋ฉด ์๋ฌ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฏธ๋ค์จ์ด๋ ์ฝ๊ฒ ์ฌ
yohanpro.com
https://jeonghwan-kim.github.io/node/2017/08/17/express-error-handling.html
์๋ฌ ์ฒ๋ฆฌ๋ฅผ ์ํ ์ต์คํ๋ ์ค ๊ฐ์ด๋
์๋ฌธ: http://thecodebarbarian.com/80-20-guide-to-express-error-handling.html ์ต์คํ๋ ์ค์ ์๋ฌ ์ฒ๋ฆฌ ๋ฏธ๋ค์จ์ด๋ HTTP ์๋ต ๋ก์ง์ ๊ฒฌ๊ณ ํ๊ฒ ๋ง๋๋ ํ์ํํ ๋๊ตฌ์ ๋๋ค. ์๋๋ ์ต์คํ๋ ์ค ์ฝ๋๋ก ์์ฑํ ์ฝ๋
jeonghwan-kim.github.io
http://expressjs.com/en/guide/error-handling.html
Express error handling
Error Handling Error Handling refers to how Express catches and processes errors that occur both synchronously and asynchronously. Express comes with a default error handler so you don’t need to write your own to get started. Catching Errors It’s impor
expressjs.com
https://stackoverflow.com/questions/13133071/express-next-function-what-is-it-really-for
Express next function, what is it really for?
Have been trying to find a good description of what the next() method does. In the Express documentation it says that next('route') can be used to jump to that route and skip all routes in between,...
stackoverflow.com
https://velog.io/@yjs_177076/next%EC%9D%98-%EC%82%AC%EC%9A%A9%EB%B2%95
next์ ์ฌ์ฉ๋ฒ
๐ต๐ซExpress์์ ์ฌ์ฉํ๋ ๋ฏธ๋ค์จ์ด ํจ์๋ฏธ๋ค์จ์ด ํจ์๋ ์์ฒญ ์ค๋ธ์ ํธ(req), ์๋ต ์ค๋ธ์ ํธ (res), ๊ทธ๋ฆฌ๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ ์์ฒญ-์๋ต ์ฃผ๊ธฐ ์ค ๊ทธ ๋ค์์ ๋ฏธ๋ค์จ์ด ํจ์ ๋ํ ์ก์ธ์ค ๊ถํ์ ๊ฐ
velog.io
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
Array.prototype.sort() - JavaScript | MDN
The sort() method sorts the elements of an array in place and returns the reference to the same array, now sorted. The default sort order is ascending, built upon converting the elements into strings, then comparing their sequences of UTF-16 code units val
developer.mozilla.org