생활 코딩 강의 목록 : 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)  
댓글 남기기