생활 코딩 강의 목록 : https://opentutorials.org/module/3590
인프런 강의 목록 : https://www.inflearn.com/course/node-js-express/dashboard
이 수업은 CC 라이센스를 따르고 있으며, 아래 링크 에서도 볼 수 있습니다.
Node.js-Express https://opentutorials.org/course/3370
쿠키와 인증 https://opentutorials.org/course/3387
세션과 인증 https://opentutorials.org/course/3400 passpor.js
https://opentutorials.org/course/3402
다중 사용자 https://opentutorials.org/course/3411 google login https://opentutorials.org/course/3413 facebook login
https://opentutorials.org/course/3414
소스 : https://github.com/braverokmc79/Nodejs-master
Express
1. 수업 소개
2. 실습환경 준비
1 ) Nodemon 설치하기
Nodemon은 프로젝트 폴더의 파일들을 모니터링 하고 있다가 파일이 수정되면 서버를 자동으로 restart 시켜주는 패키지이다.
다음 명령어를 이용하여 설치한다. (-dev를 붙이면, development mode, 즉 local에서만 사용하겠다는 의미)
npm install nodemon --save-dev
scripts 에 아래 스크립트를 추가해준다.
"dev"부분에는 본인이 원하는 name을 넣으면 된다.
"dev" : "nodemon main.js",
npm run dev 명령어로 서버를 실행시킨다.
2) sanitize-html 설치
$ npm i sanitize-html 또는 $ yarn add sanitize-html
$ npm run dev
3. Hello world 1
expressjs 공식문서 설치 : https://expressjs.com/en/starter/installing.html
Express 설치는 npm 커맨드를 사용해 인스톨
npm i express
yarn 사용시
$ yarn add express
expressjs 공식문서 Hello world : https://expressjs.com/en/starter/hello-world.html
const express = require('express') const app = express() const port = 3000 app.get('/', (req, res) => { res.send('Hello World!') }) app.listen(port, () => { console.log(`Example app listening on port ${port}`) })
4. Hello world 2
main.js
const express = require('express') const app = express() const port = 3000 app.get('/', (req, res) => { res.send('Hello World!') }) app.get('/page', (req, res) => { res.send("/page"); }); app.listen(port, () => { console.log(`Example app listening on port ${port}`) })
5. Hello world 2
main.js
const express = require('express') const template = require('./lib/template.js') const fs = require('fs'); const app = express() const port = 3000 app.get('/', (req, res) => { fs.readdir('./data', function (error, filelist) { const title = 'Welcome'; const description = 'Hello, Node.js'; const list = template.list(filelist); const html = template.HTML(title, list, `<h2>${title}</h2>${description}`, `<a href="/create">create</a>` ); res.send(html); }); })
6. 상세보기 페이지 구현 1
main.js
app.get('/page/:pageId', (req, res) => { res.send(req.params); });
http://localhost:3000/page/HTML
{ "pageId": "HTML" }
7. 상세보기 페이지 구현 2
main,js
const express = require('express') const template = require('./lib/template.js'); const path = require('path'); const sanitizeHtml = require('sanitize-html'); const fs = require('fs'); const app = express() const port = 3000 app.get('/page/:pageId', (req, res) => { fs.readdir('./data', function (error, filelist) { const filteredId = path.parse(req.params.pageId).base; fs.readFile(`data/${filteredId}`, 'utf8', function (err, description) { const title = filteredId; const sanitizedTitle = sanitizeHtml(title); const sanitizedDescription = sanitizeHtml(description, { allowedTags: ['h1'] }); const list = template.list(filelist); const html = template.HTML(sanitizedTitle, list, `<h2>${sanitizedTitle}</h2>${sanitizedDescription}`, ` <a href="/create">create</a> <a href="/update?id=${sanitizedTitle}">update</a> <form action="delete_process" method="post"> <input type="hidden" name="id" value="${sanitizedTitle}"> <input type="submit" value="delete"> </form>` ); res.send(html); }); }); });
lib/template.js
list: function (filelist) { var list = '<ul>'; var i = 0; while (i < filelist.length) { list = list + `<li><a href="/page/${filelist[i]}">${filelist[i]}</a></li>`; i = i + 1; } list = list + '</ul>'; return list; }
8. 페이지 생성 구현
main.js
app.post("/create_process", (req, res) => { let body = ''; req.on('data', function (data) { body = body + data; }); req.on('end', function () { const post = qs.parse(body); const title = post.title; const description = post.description; fs.writeFile(`data/${title}`, description, 'utf8', function (err) { res.redirect(`/page/${title}`); }) }); })
9. 페이지 수정 기능 구현
경로 수정
main.js
app.get('/page/:pageId', (req, res) => { ~ <a href="/update/${sanitizedTitle}">update</a> ~
main.js
app.get("/update/:pageId", (req, res) => { fs.readdir('./data', function (error, filelist) { const filteredId = path.parse(req.params.pageId).base; fs.readFile(`data/${filteredId}`, 'utf8', function (err, description) { const title = filteredId; const list = template.list(filelist); const html = template.HTML(title, list, ` <form action="/update_process" method="post"> <input type="hidden" name="id" value="${title}"> <p><input type="text" name="title" placeholder="title" value="${title}"></p> <p> <textarea name="description" placeholder="description">${description}</textarea> </p> <p> <input type="submit"> </p> </form> `, `<a href="/create">create</a> <a href="/update/${title}">update</a>` ); res.send(html); }); }); }); app.post("/update_process", (req, res) => { let body = ''; req.on('data', function (data) { body = body + data; }); req.on('end', function () { const post = qs.parse(body); const id = post.id; const title = post.title; const description = post.description; fs.rename(`data/${id}`, `data/${title}`, function (error) { fs.writeFile(`data/${title}`, description, 'utf8', function (err) { res.redirect(`/page/${title}`); }) }); }); })
10. 페이지 삭제 기능 구현
main.js
~ app.get('/page/:pageId', (req, res) => { ~ <a href="/update/${sanitizedTitle}">update</a> <form action="/delete_process" method="post"> <input type="hidden" name="id" value="${sanitizedTitle}"> <input type="submit" value="delete"> </form>` ~
main.js
app.post("/delete_process", (req, res) => { let body = ''; req.on('data', function (data) { body = body + data; }); req.on('end', function () { const post = qs.parse(body); const id = post.id; const filteredId = path.parse(id).base; fs.unlink(`data/${filteredId}`, function (error) { res.redirect("/"); }) }); });
11. 미들웨어의 사용 - body parser
body-parser
https://www.npmjs.com/package/body-parser
https://github.com/expressjs/body-parser
설치 :
$ npm i body-parser
main.js
const express = require('express') const template = require('./lib/template.js'); const path = require('path'); const sanitizeHtml = require('sanitize-html'); const fs = require('fs'); const bodyParser = require('body-parser') const app = express() const port = 3000 app.use(bodyParser.urlencoded({ extended: false })); app.post("/create_process", (req, res) => { const post = req.body; const title = post.title; const description = post.description; fs.writeFile(`data/${title}`, description, 'utf8', function (err) { res.redirect(`/page/${title}`); }); }) app.post("/update_process", (req, res) => { const post = req.body; const id = post.id; const title = post.title; const description = post.description; fs.rename(`data/${id}`, `data/${title}`, function (error) { fs.writeFile(`data/${title}`, description, 'utf8', function (err) { res.redirect(`/page/${title}`); }) }); }) app.post("/delete_process", (req, res) => { const post = req.body; const id = post.id; const filteredId = path.parse(id).base; fs.unlink(`data/${filteredId}`, function (error) { res.redirect("/"); }) });
12. 미들웨어의 사용 - compression
https://www.npmjs.com/package/compression
$ npm i compression
const express = require('express') const template = require('./lib/template.js'); const path = require('path'); const sanitizeHtml = require('sanitize-html'); const fs = require('fs'); const bodyParser = require('body-parser') const compression = require('compression') const app = express() const port = 3000 app.use(bodyParser.urlencoded({ extended: false })); app.use(compression());
13. Express 미들웨어 만들기
Using middleware
공식문서 : http://expressjs.com/en/guide/using-middleware.html
커스텀 미들웨어 생성
app.get('*', (req, res, next) => { fs.readdir('./data', function (error, filelist) { req.list = filelist; next(); }); });
main.js
const express = require('express') const template = require('./lib/template.js'); const path = require('path'); const sanitizeHtml = require('sanitize-html'); const fs = require('fs'); const bodyParser = require('body-parser') const compression = require('compression') const app = express() const port = 3000 app.use(bodyParser.urlencoded({ extended: false })); app.use(compression()); app.get('*', (req, res, next) => { fs.readdir('./data', function (error, filelist) { req.list = filelist; next(); }); }); app.get('/', (req, res) => { const title = 'Welcome'; const description = 'Hello, Node.js'; const list = template.list(req.list); const html = template.HTML(title, list, `<h2>${title}</h2>${description}`, `<a href="/create">create</a>` ); res.send(html); }) app.get('/page/:pageId', (req, res) => { const filteredId = path.parse(req.params.pageId).base; fs.readFile(`data/${filteredId}`, 'utf8', function (err, description) { const title = filteredId; const sanitizedTitle = sanitizeHtml(title); const sanitizedDescription = sanitizeHtml(description, { allowedTags: ['h1'] }); const list = template.list(req.list); const html = template.HTML(sanitizedTitle, list, `<h2>${sanitizedTitle}</h2>${sanitizedDescription}`, ` <a href="/create">create</a> <a href="/update/${sanitizedTitle}">update</a> <form action="/delete_process" method="post"> <input type="hidden" name="id" value="${sanitizedTitle}"> <input type="submit" value="delete"> </form>` ); res.send(html); }); }); app.get("/create", (req, res) => { const title = 'WEB - create'; const list = template.list(req.list); const html = template.HTML(title, list, ` <form action="/create_process" method="post"> <p><input type="text" name="title" placeholder="title"></p> <p> <textarea name="description" placeholder="description"></textarea> </p> <p> <input type="submit"> </p> </form> `, ''); res.send(html); }); app.post("/create_process", (req, res) => { const post = req.body; const title = post.title; const description = post.description; fs.writeFile(`data/${title}`, description, 'utf8', function (err) { res.redirect(`/page/${title}`); }); }) app.get("/update/:pageId", (req, res) => { const filteredId = path.parse(req.params.pageId).base; fs.readFile(`data/${filteredId}`, 'utf8', function (err, description) { const title = filteredId; const list = template.list(req.list); const html = template.HTML(title, list, ` <form action="/update_process" method="post"> <input type="hidden" name="id" value="${title}"> <p><input type="text" name="title" placeholder="title" value="${title}"></p> <p> <textarea name="description" placeholder="description">${description}</textarea> </p> <p> <input type="submit"> </p> </form> `, `<a href="/create">create</a> <a href="/update/${title}">update</a>` ); res.send(html); }); }); app.post("/update_process", (req, res) => { const post = req.body; const id = post.id; const title = post.title; const description = post.description; fs.rename(`data/${id}`, `data/${title}`, function (error) { fs.writeFile(`data/${title}`, description, 'utf8', function (err) { res.redirect(`/page/${title}`); }) }); }) app.post("/delete_process", (req, res) => { const post = req.body; const id = post.id; const filteredId = path.parse(id).base; fs.unlink(`data/${filteredId}`, function (error) { res.redirect("/"); }) }); app.listen(port, () => { console.log(`Example app listening on port ${port}`) })
14. Express 미들웨어의 실행순서
15. 정적인 파일의 서비스
express 공식문서 : http://expressjs.com/en/starter/static-files.html
app.get('/', (req, res) => { const title = 'Welcome'; const description = 'Hello, Node.js'; const list = template.list(req.list); const html = template.HTML(title, list, `<h2>${title}</h2>${description} <img src="/images/hello.jpg" style="width:300px; display:block; margin-top:10px" > `, `<a href="/create">create</a>` ); res.send(html); })
16. 에러처리
공식문서 : https://expressjs.com/en/guide/error-handling.html
main.js 하단에
app.use(function (req, res) { res.status(400).send("Sorry cant find that!"); }); app.use((err, req, res, next) => { console.error(err.stack) res.status(500).send('Something broke!') })
17. 라우터 - 주소체계변경
공식문서 : http://expressjs.com/en/api.html#router
main.js
const express = require('express') const template = require('./lib/template.js'); const path = require('path'); const sanitizeHtml = require('sanitize-html'); const fs = require('fs'); const bodyParser = require('body-parser') const compression = require('compression') const app = express() const port = 3000 app.use(express.static('public')) app.use(bodyParser.urlencoded({ extended: false })); app.use(compression()); app.get('*', (req, res, next) => { fs.readdir('./data', function (error, filelist) { req.list = filelist; next(); }); }); app.get('/', (req, res) => { const title = 'Welcome'; const description = 'Hello, Node.js'; const list = template.list(req.list); const html = template.HTML(title, list, `<h2>${title}</h2>${description} <img src="/images/hello.jpg" style="width:300px; display:block; margin-top:10px" > `, `<a href="/topic/create">create</a>` ); res.send(html); }) app.get("/topic/create", (req, res) => { const title = 'WEB - create'; const list = template.list(req.list); const html = template.HTML(title, list, ` <form action="/topic/create_process" method="post"> <p><input type="text" name="title" placeholder="title"></p> <p> <textarea name="description" placeholder="description"></textarea> </p> <p> <input type="submit"> </p> </form> `, ''); res.send(html); }); app.post("/topic/create_process", (req, res) => { const post = req.body; const title = post.title; const description = post.description; fs.writeFile(`data/${title}`, description, 'utf8', function (err) { res.redirect(`/topic/${title}`); }); }); app.get("/topic/update/:pageId", (req, res) => { const filteredId = path.parse(req.params.pageId).base; fs.readFile(`data/${filteredId}`, 'utf8', function (err, description) { const title = filteredId; const list = template.list(req.list); const html = template.HTML(title, list, ` <form action="/topic/update_process" method="post"> <input type="hidden" name="id" value="${title}"> <p><input type="text" name="title" placeholder="title" value="${title}"></p> <p> <textarea name="description" placeholder="description">${description}</textarea> </p> <p> <input type="submit"> </p> </form> `, `<a href="/topic/create">create</a> <a href="/topic/update/${title}">update</a>` ); res.send(html); }); }); app.post("/topic/update_process", (req, res) => { const post = req.body; const id = post.id; const title = post.title; const description = post.description; fs.rename(`data/${id}`, `data/${title}`, function (error) { fs.writeFile(`data/${title}`, description, 'utf8', function (err) { res.redirect(`/topic/${title}`); }) }); }) app.post("/topic/delete_process", (req, res) => { const post = req.body; const id = post.id; const filteredId = path.parse(id).base; fs.unlink(`data/${filteredId}`, function (error) { res.redirect("/"); }) }); app.get('/topic/:pageId', (req, res, next) => { const filteredId = path.parse(req.params.pageId).base; fs.readFile(`data/${filteredId}`, 'utf8', function (err, description) { if (err) return next(err); const title = filteredId; const sanitizedTitle = sanitizeHtml(title); const sanitizedDescription = sanitizeHtml(description, { allowedTags: ['h1'] }); const list = template.list(req.list); const html = template.HTML(sanitizedTitle, list, `<h2>${sanitizedTitle}</h2>${sanitizedDescription}`, ` <a href="/topic/create">create</a> <a href="/topic/update/${sanitizedTitle}">update</a> <form action="/topic/delete_process" method="post"> <input type="hidden" name="id" value="${sanitizedTitle}"> <input type="submit" value="delete"> </form>` ); res.send(html); }); }); app.use(function (req, res) { res.status(400).send("Sorry cant find that!"); }); app.use((err, req, res, next) => { console.error(err.stack) res.status(500).send('Something broke!') }) app.listen(port, () => { console.log(`Example app listening on port ${port}`) })
18. 라우터 - 파일로 분리 1
main.js
const express = require('express') const template = require('./lib/template.js'); const fs = require('fs'); const bodyParser = require('body-parser') const compression = require('compression') const topicRouter = require("./routes/topic"); const app = express() const port = 3000 app.use(express.static('public')) app.use(bodyParser.urlencoded({ extended: false })); app.use(compression()); app.get('*', (req, res, next) => { fs.readdir('./data', function (error, filelist) { req.list = filelist; next(); }); }); app.use('/topic', topicRouter); ~ ~
/routes/topic.js
const express = require('express'); const router = express.Router(); const template = require('../lib/template.js'); const path = require('path'); const sanitizeHtml = require('sanitize-html'); const fs = require('fs'); router.get("/create", (req, res) => { const title = 'WEB - create'; const list = template.list(req.list); const html = template.HTML(title, list, ` <form action="/topic/create_process" method="post"> <p><input type="text" name="title" placeholder="title"></p> <p> <textarea name="description" placeholder="description"></textarea> </p> <p> <input type="submit"> </p> </form> `, ''); res.send(html); }); ~~ `
20. 라우터 - 파일로 분리 2
main.js
const express = require('express') const template = require('./lib/template.js'); const fs = require('fs'); const bodyParser = require('body-parser') const compression = require('compression') const indexRouter = require("./routes/index"); const topicRouter = require("./routes/topic"); const app = express() const port = 3000 app.use(express.static('public')) app.use(bodyParser.urlencoded({ extended: false })); app.use(compression()); app.get('*', (req, res, next) => { fs.readdir('./data', function (error, filelist) { req.list = filelist; next(); }); }); app.use("/", indexRouter); app.use('/topic', topicRouter); app.use(function (req, res) { res.status(400).send("Sorry cant find that!"); }); app.use((err, req, res, next) => { console.error(err.stack) res.status(500).send('Something broke!') }) app.listen(port, () => { console.log(`Example app listening on port ${port}`) })
routes/index.js
const express = require('express'); const router = express.Router(); const template = require('../lib/template.js'); router.get('/', (req, res) => { const title = 'Welcome'; const description = 'Hello, Node.js'; const list = template.list(req.list); const html = template.HTML(title, list, `<h2>${title}</h2>${description} <img src="/images/hello.jpg" style="width:300px; display:block; margin-top:10px" > `, `<a href="/topic/create">create</a>` ); res.send(html); }); module.exports = router;
21. 보안
보안 : http://expressjs.com/en/advanced/best-practice-security.html
Use Helmet
공식문서 : http://expressjs.com/en/advanced/best-practice-security.html#use-helmet
$ npm install --save helmet
Then to use it in your code:
// ... const helmet = require('helmet') app.use(helmet()) // ...
애플리케이션의 취약점을 테스트 snyk
https://docs.snyk.io/getting-started/create-a-snyk-account
npm을 사용하여 애플리케이션의 종속성을 관리하는 것은 강력하고 편리합니다. 그러나 사용하는 패키지에는 애플리케이션에도 영향을 줄 수 있는 심각한 보안 취약점이 포함될 수 있습니다. 앱의 보안은 종속성에서 "가장 약한 링크"만큼만 강력합니다.
npm@6 이후로 npm은 모든 설치 요청을 자동으로 검토합니다. 또한 'npm 감사'를 사용하여 종속성 트리를 분석할 수 있습니다.
$ npm audit
더 안전하게 유지하려면 Snyk 를 고려하십시오 .
Snyk는 명령줄 도구 와 Github 통합 을 제공하여 Snyk의 오픈 소스 취약성 데이터베이스 에 대해 응용 프로그램에서 종속성의 알려진 취약성을 확인합니다. 다음과 같이 CLI를 설치합니다.
$ npm install -g snyk $ cd your-app
다음 명령을 사용하여 애플리케이션의 취약점을 테스트합니다.
$ snyk test
이 명령을 사용하여 발견된 취약점을 수정하기 위해 업데이트 또는 패치를 적용하는 프로세스를 안내하는 마법사를 엽니다.
$ snyk wizard
22. express generator
Express application generator
공식 문서 : http://expressjs.com/en/starter/generator.html
$ npm install -g express-generator
애플리케이션 생성기 도구인 express-generator를 사용하여 애플리케이션 스켈레톤을 빠르게 만듭니다.
npx 명령어(Node.js 8.2.0에서 사용 가능)로 애플리케이션 생성기를 실행할 수 있습니다.
$ npx express-generator
이전 노드 버전의 경우 애플리케이션 생성기를 전역 npm 패키지로 설치한 다음 실행합니다.
$ npm install -g express-generator $ express
-h 옵션을 사용하여 명령 옵션을 표시합니다.
$ express -h Usage: express [options] [dir] Options: -h, --help output usage information --version output the version number -e, --ejs add ejs engine support --hbs add handlebars engine support --pug add pug engine support -H, --hogan add hogan.js engine support --no-view generate without view engine -v, --view <engine> add view <engine> support (ejs|hbs|hjs|jade|pug|twig|vash) (defaults to jade) -c, --css <engine> add stylesheet <engine> support (less|stylus|compass|sass) (defaults to plain css) --git add .gitignore -f, --force force on non-empty directory
예를 들어 다음은 myapp이라는 Express 앱을 만듭니다. 앱은 현재 작업 디렉터리의 myapp이라는 폴더에 생성되고 보기 엔진은 Pug로 설정됩니다.
$ express --view=pug myapp create : myapp create : myapp/package.json create : myapp/app.js create : myapp/public create : myapp/public/javascripts create : myapp/public/images create : myapp/routes create : myapp/routes/index.js create : myapp/routes/users.js create : myapp/public/stylesheets create : myapp/public/stylesheets/style.css create : myapp/views create : myapp/views/index.pug create : myapp/views/layout.pug create : myapp/views/error.pug create : myapp/bin create : myapp/bin/www
그런 다음 종속성을 설치합니다.
$ cd myapp $ npm install
MacOS 또는 Linux에서 다음 명령으로 앱을 실행합니다.
$ DEBUG=myapp:* npm start
Windows 명령 프롬프트에서 다음 명령을 사용합니다.
> set DEBUG=myapp:* & npm start
Windows PowerShell에서 다음 명령을 사용합니다.
PS> $env:DEBUG='myapp:*'; npm start
그런 다음 브라우저에서 http://localhost:3000/을 로드하여 앱에 액세스합니다.
생성된 앱의 디렉터리 구조는 다음과 같습니다.
. ├── app.js ├── bin │ └── www ├── package.json ├── public │ ├── images │ ├── javascripts │ └── stylesheets │ └── style.css ├── routes │ ├── index.js │ └── users.js └── views ├── error.pug ├── index.pug └── layout.pug 7 directories, 9 files
23. 수업을 마치며
댓글 ( 4)
댓글 남기기