[Day 31] Express + EJS + Web Form + 클래스(1)

Express

  • Express Official Manual
  • Node.js 생태계에서 가장 널리 쓰이는 웹 프레임워크
  • 내장하고 있는 기능은 매우 적으나, 미들웨어를 주입하는 방식으로 기능을 확장하는 생태계를 가지고 있다.

Express 앱의 기본 구조

// Express 인스턴스 생성
const app = express()

// 미들웨어 주입
app.use(sessionMiddleware())
app.use(authenticationMiddleware())

// 라우트 핸들러 등록
app.get('/', (request, response) => {
  response.send('Hello express!')
})

// 서버 구동
app.listen(3000, () => {
  console.log('Example app listening on port 3000!')
})

Routing

// HTTP 요청 메소드(GET, POST, ...)와 같은 이름의 메소드를 사용
app.get('/articles', (req, res) => {
  res.send('Hello Routing!')
})
// 특정 경로에만 미들웨어를 주입하는 것도 가능
app.post('/articles', bodyParserMiddleware(), (req, res) => {
  database.articles.create(req.body)
    .then(() => {
      res.send({ok: true})
    })
})
// 경로의 특정 부분을 함수의 인자처럼 입력받을 수 있음
app.get('/articles/:id', (req, res) => {
  database.articles.find(req.params.id) // `req.params`에 저장됨
    .then(article => {
      res.send(article)
    })
})

Request 객체

  • 요청(request)의 구성요소
    • 메소드
    • 주소
    • 헤더
    • 바디
  • req.body
    • 요청 바디를 적절한 형태의 자바스크립트 객체로 변환하여 이곳에 저장한다. (body-parser 미들웨어에 의해 처리된다.)
  • req.ip
    • 요청한 쪽의 IP
  • req.params
    • route parameter
  • req.query
    • query string이 객체로 저장된다.
  • req.get(…)
    • 요청에 표함된 특정 헤더의 값을 가져온다.
    • ex) req.get('X-Custom-Header')

Response 객체

  • 응답(response)의 구성요소
    • 상태코드
    • 헤더
    • 바디
  • res.status(…)
    • 응답의 상태 코드를 지정하는 메소드
  • res.append(…)
    • 응답의 헤더를 지정하는 메소드
  • res.send(…)
    • 응답의 바디를 지정하는 메소드
    • 인자가 텍스트면 text/html, 객체면 application/json 타입으로 응답한다.
  • res.set(…)
    • 응답에 새로운 헤더를 지정한다.
    • ex) res.set('X-Custom-Header', value)
  • res.end()
    • 응답을 보낸다.
    • res.send(…)와 비슷하지만, 아무런 인자를 받지 않고 그냥 보낸다.

Template Language

Template Engine

  • 템플릿과 데이터를 결합해 문서를 생성하는 프로그램, 혹은 라이브러리
  • 템플릿을 작성할 때 사용하는 언어를 템플릿 언어라고 한다.

EJS

  • Embedded JavaScript Template

  • Node.js 생태계에서 가장 많이 사용되는 템플릿 엔진

  • JavaScript 코드를 템플릿 안에서 그대로 쓸 수 있음

  • EJS VSCode Extension

  • EJS에서 Emmet 사용하기

  • EJS 사용 예시

    <%# index.ejs %>
    <html>
      <head>
        <title><%= title %></title>
      </head>
      <body>
        <div class="message">
          <%= message %>
        </div>
        <% if (showSecret) { %>
          <div>my secret</div>
        <% } %>
      </body>
    </html>
    
  • EJS는 서버에서 HTML을 만져서 프론트로 응답을 넘기는 방식이다.

    • 템플릿과 데이터를 합쳐서 HTML을 만들어낸 뒤 응답으로 보낸다.
    • react, angular 등 프론트엔드 라이브러리(프레임워크)의 등장으로 이러한 방식의 변화가 일어난다. (서버가 아닌 브라우저에서 HTML을 만지는 방식으로 변화한다.)

Web Form

HTML form의 기본 동작

  • HTML form을 전송하면, 입력된 정보가 기본적으로 percent encoding 되어 요청된다.

    • GET Method : 입력된 정보가 query string 으로 날라간다.

      GET /search?query=%EA%B0%9C&sort=latest HTTP/1.1
      ...
      
    • POST Method : 입력된 정보가 요청 바디에 포함되서 날라간다.

      POST /form HTTP/1.1
      Content-Type: application/x-www-form-urlencoded 
      // Content-Type은 요청 바디에 포함되어 있는 정보가 어떤 형식인지 보여준다.
      ...
          
      home=Cosby&favorite+flavor=flies
      
  • 전송된 문서의 타입(MIME 타입)

UUID

  • 일종의 난수 형태를 띄는 식별자로 사용하기 위해 고안된 수 형식

  • node에서 UUID를 사용하기 위해서는 npm 패키지 uuid를 사용하면 된다.

    const uuidv4 = require('uuid/v4')
      
    const todos = [
        {
            id: uuidv4(),	// 함수를 실행하면 난수 문자열이 return 된다.
            title: 'sample todo'
        }
    ]
    

Redirect after submission (전통적인 웹 개발에서)

  • form을 submit 하고난 뒤 redirect 시켜주지 않으면 새로고침 할 경우 이전과 같은 요청이 한번 더 가게된다.
  • form에서 post 요청을 보냈을 경우 redirect 시켜주지 않고 새로고침 하면 계속 post 요청을 보내는 결과를 만든다.
  • redirect를 통해 post 요청을 완료시키고 get 요청으로 우회해서 페이지를 보여주는 것이 가능하다.

클래스 (Class)

Class (ES2015)

  • ES2015 이전에는 비슷한 종류의 객체를 만들어내기 위해 생성자(Constructor)를 사용했었다.

  • 구버전 브라우저에서도 클래스를 사용할 수 있도록 하기 위해 클래스는 생성자를 기반으로 만들어졌고, 생성자의 기능을 대체한다. (Transpiler를 통해 클래스는 모두 생성자로 변경된다.)

  • 클래스 사용 예제

    // 클래스
    class Person {
      // 이전에서 사용하던 생성자 함수는 클래스 안에 `constructor`라는 이름으로 정의합니다.
      constructor({name, age}) {
        this.name = name;
        this.age = age;
      }
      
      // 객체에서 메소드를 정의할 때 사용하던 문법을 그대로 사용하면, 메소드가 자동으로 `Person.prototype`에 저장됩니다.
      introduce() {
        return `안녕하세요, 제 이름은 ${this.name}입니다.`;
      }
    }
      
    const person = new Person({name: '윤아준', age: 19});
    console.log(person.introduce()); // 안녕하세요, 제 이름은 윤아준입니다.
    console.log(typeof Person); // function
    console.log(typeof Person.prototype.constructor); // function
    console.log(typeof Person.prototype.introduce); // function
    console.log(person instanceof Person); // true
    
  • class 블록을 정의할 때에는 기존의 JS와는 다른 별도의 문법으로 코드를 작성해야 한다.

    // 클래스는 함수가 아닙니다!
    class Person {
      console.log('hello');
    }
    // 에러: Unexpected token
    
    // 클래스는 객체가 아닙니다!
    class Person {
      prop1: 1,
      prop2: 2
    }
    // 에러: Unexpected token
    
  • 생성자와 클래스의 동작방식의 차이점

    • 클래스는 함수로 호출될 수 없다.
    • 클래스 선언은 letconst 처럼 블록 스코프에 선언되며, 호이스팅이 일어나지 않는다.
    • 클래스의 메소드 안에서 super 키워드를 사용할 수 있다.

메소드 정의하기

class Calculator {
  add(x, y) {
    return x + y;
  }
  subtract(x, y) {
    return x - y;
  }
}
  • 임의의 표현식을 대괄호로 둘러싸서 메소드의 이름으로 사용할 수도 있다.

    const methodName = 'introduce';
    class Person {
      constructor({name, age}) {
        this.name = name;
        this.age = age;
      }
      // 아래 메소드의 이름은 `introduce`가 됩니다.
      [methodName]() {
        return `안녕하세요, 제 이름은 ${this.name}입니다.`;
      }
    }
      
    console.log(new Person({name: '윤아준', age: 19}).introduce()); // 안녕하세요, 제 이름은 윤아준입니다.
    
  • Getter / Setter 도 정의할 수 있다.

    class Account {
      constructor() {
        this._balance = 0;
      }
      get balance() {
        return this._balance;
      }
      set balance(newBalance) {
        this._balance = newBalance;
      }
    }
      
    const account = new Account();
    account.balance = 10000;
    account.balance; // 10000
    
  • static 키워드를 메소드 이름 앞에 붙여주면 해당 메소드는 정적 메소드가 된다.

    class Person {
      constructor({name, age}) {
        this.name = name;
        this.age = age;
      }
      // 이 메소드는 정적 메소드입니다.
      static sumAge(...people) {
        return people.reduce((acc, person) => acc + person.age, 0);
      }
    }
      
    const person1 = new Person({name: '윤아준', age: 19});
    const person2 = new Person({name: '신하경', age: 20});
      
    Person.sumAge(person1, person2); // 39
    
  • Generator 메소드를 정의하려면 메소드 이름 앞에 * 를 붙여주면 된다.

    • Symbol.iterator 메소드를 generator로 정의하면 클래스의 인스턴스를 iterable로 만들 수 있다.

      class Gen {
        *[Symbol.iterator]() {
          yield 1;
          yield 2;
          yield 3;
        }
      }
          
      // 1, 2, 3이 차례대로 출력됩니다.
      for (let n of new Gen()) {
        console.log(n);
      }
      

클래스 필드 (Class Field)

  • 클래스 블록 안에서 할당 연산자 = 를 이용해 인스턴스 속성을 지정할 수 있는 문법.

    class Counter {
      static initial = 0; // static class field
      count = Counter.initial; // class field
      inc() {
        return this.count++;
      }
    }
      
    const counter = new Counter();
    console.log(counter.inc()); // 0
    console.log(counter.inc()); // 1
      
    Counter.initial = 10;
    console.log(new Counter().count); // 10
    
  • 클래스 필드는 아직 정식 표준 기능은 아니지만 많이 사용하고 있다. 현재는 트랜스파일러를 이용해 이 기능을 사용할 수 있다.

클래스 필드와 this

  • class 블록은 새로운 블록 스코프를 만든다. 그리고 이 내부에서 사용된 this는 인스턴스 객체를 가리키게 된다.

    class MyClass {
      a = 1;
      b = this.a;
    }
      
    new MyClass().b; // 1
    
  • 이러한 성질을 이용해서 화살표 함수를 통해서 메소드를 정의할 수 있다.

    • 메소드를 다른 함수의 인수로 넘겨줘야 하는 경우 화살표 함수를 사용하는 것이 좋다.
    class MyClass {
      a = 1;
      getA = () => {
        return this.a;
      }
    }
      
    new MyClass().getA(); // 1
    
    • 일반적인 메소드는 클래스의 prototype 속성에 저장되지만, 클래스 필드는 인스턴스 객체에 저장된다.
    • 화살표 함수의 this는 호출 형태에 관계없이 항상 인스턴스 객체를 가리키게 된다.
      • 이러한 성질 때문에 메소드를 값으로 다뤄야 할 경우 화살표 함수를 사용하는 경우가 있다.
      • 클래스 필드를 통해 정의한 메소드는 인스턴스를 생성할 때마다 새로 생성되기 때문에 메모리를 많이 차지한다.

댓글남기기