본문 바로가기
활동/서울시뉴딜일자리 IT인턴

[뉴딜IT인턴] 9일차 - 자바스크립트 서버 프레임워크(NodeJS) 실습

by gardenii 2024. 5. 20.

1. Node.js란?

  • 웹 브라우저 바깥에서(윈도우, 리눅스, macOS 등) 자바스크립트 코드를 실행할 수 있는 자바스크립트 런타임 환경
  • 자바스크립트 런타임 환경웹 브라우저 환경에서는 웹 페이지에 대한 사용자 인터페이스를 만들고, 사용자와의 상호작용을 관리하며, 웹 서버와의 통신을 처리하는데 사용됩니다. 브라우저의 자바스크립트 런타임 환경은 DOM(Document Object Model), 웹 API (예: Fetch API, Web Storage API 등), 이벤트 루프 및 콜 스택과 같은 요소를 포함합니다.따라서 자바스크립트 런타임 환경은 자바스크립트 코드가 실행되는 특정 컨텍스트를 제공하며, 코드가 어떻게 실행되고, 어떤 API가 사용 가능한지, 코드가 어떻게 스케줄링되고 관리되는지 등을 결정합니다.
  • 반면에, Node.js는 웹 브라우저 바깥에서 자바스크립트를 실행할 수 있는 자바스크립트 런타임 환경입니다. 이는 서버 측 애플리케이션 개발에 널리 사용되며, 파일 시스템 작업, 데이터베이스 상호작용, 네트워크 요청 처리 등을 수행할 수 있습니다. Node.js 런타임은 V8 자바스크립트 엔진, 이벤트 루프, Node.js API (예: fs, http, crypto 등) 및 콜 스택을 포함합니다.
  • 자바스크립트 런타임 환경은 자바스크립트 코드를 실행하는 데 필요한 모든 소프트웨어 구성 요소를 포함합니다. 이는 웹 브라우저 또는 Node.js와 같은 서버 측 환경을 포함할 수 있습니다.
  • 웹서버가 아님. 노드 환경에서 웹서버를 만드는 것
  • 특징
    • 비동기 I/O 처리(Non-blocking), 이벤트 기반
      • 순서대로 처리하지 않음
      • 지켜보는 방식/알려주는 방식(이벤트)
      • 입출력 비동기 처리
    • 빠른 속도
    • 단일 쓰레드
      • CPU가 프로세스 단위로 작업을 처리할 때, 프로세스 내부에서 동시(같은) 작업을 할 수 있도록 하는 작업 단위
      • 멀티 쓰레드 사용 : 효율 높이기 위해
      • 프로그래밍 언어 별 쓰레드다른 프로그래밍 언어들, 특히 서버 사이드에서 사용되는 언어들은 다양한 방식으로 구성됩니다. 예를 들어, Java는 멀티 쓰레드를 사용하여 복잡한 서버 애플리케이션을 개발할 수 있습니다. 이는 각 요청마다 새로운 쓰레드를 생성하므로, 동시에 여러 요청을 처리하는 데 유용할 수 있습니다. 그러나, 이는 메모리와 CPU 자원을 많이 사용하게 됩니다.Python은 동기식 프로그래밍 모델을 사용하며, 이는 코드의 실행 순서를 명확하게 만들어 줍니다. 그러나, 이는 I/O 작업 도중 블로킹이 발생하게 만들어, 효율성을 저하시킬 수 있습니다.
      • 따라서, 각 언어와 환경은 특정한 유형의 문제를 해결하기 위해 설계되었습니다. 그러므로, 어떤 언어를 사용할 지는 개발하려는 애플리케이션의 종류와 요구 사항에 따라 달라집니다.
      • PHP는 다른 접근 방식을 취하며, 각 웹 요청마다 새로운 스크립트 인스턴스를 생성합니다. 이 방식은 상태를 관리하는 것이 더 쉽지만, 이는 웹 서버에 많은 부하를 줄 수 있습니다.
      • Node.js는 단일 쓰레드를 사용하는데, 이는 메모리 소비가 적고, 복잡한 동기화 문제를 피할 수 있기 때문입니다. 또한, Node.js의 주요 사용 사례 중 하나는 동시에 많은 수의 연결을 처리하는 것이므로, 쓰레드 기반의 서버보다 효율적입니다. 이는 각 연결마다 새로운 쓰레드를 할당하는 것은 메모리와 CPU를 많이 사용하게 되는 반면, Node.js의 이벤트 드리븐, 비동기적 접근 방식은 이런 부담을 크게 줄여줍니다.
  • 사용하기 좋은 어플리케이션
    • JSON API 기반 어플리케이션
    • 싱글페이지 어플리케이션 (SPA)
    • 입출력 잦은 거
    • 데이터 스트리밍
    • 데이터 실시간 다루는 것

2. 자바스크립트 기본

2-1. 변수 선언자

  • var : 데이터 타입에 관계 없이 선언 가능, 동일 변수명 재선언 가능 
  • let : var과 동일하나 동일 변수명 재선언 불가
  • const : 동일 변수명 재선언, 변수 값 변경 불가

2-2. 함수

❓ 함수란?
⇒ 일정한 동작을 수행하는 코드

❓왜 사용하는가?

⇒ 반복되는 일 쉽게 처리하기 
⇒ 코드 깔끔히 정리하기
⇒ 실수 줄이기 

 

function funName() {
  console.log("hello");
}

const func1 = (name) => {
  return "Hello " + name;
};

const func2 = (name) => "Hello " + name;

funName();
console.log(func1("정원"));
console.log(func2("Jeongwon"));

// 익명함수 -> 화살표 함수
() => {
  console.log("화살표 함수, 람다 함수");
};

// 실행하는 법
(() => {
  console.log("화살표 함수, 람다 함수");
})();

let sum = (a, b) => a + b;

console.log("합", sum(10, 20));

2-3. 템플릿 리터럴

백틱과 변수명을 사용하여 문자열과 병합하여 사용

let userName = "Jeongwon";

console.log(`hello ${userName}`);

2-4. 구조분해

객체나 배열에서 원하는 값을 추출하여 변수에 할당하는 방법

const person = {
  name: 'Alice',
  age: 30,
  city: 'Wonderland'
};

// 기존 방식
const name = person.name;
const age = person.age;
const city = person.city;

// 구조 분해 방식
const { name, age, city } = person;
// 함수 정의 시 상자 사용
function greet({ name, age }) {
  console.log(`Hello, ${name}! You are ${age} years old.`);
}

const person = {
  name: 'Bob',
  age: 25
};

greet(person); // 'Hello, Bob! You are 25 years old.'
// 함수 정의 시 상자 사용
function greet({ name, age }) {
  console.log(`Hello, ${name}! You are ${age} years old.`);
}

const person = {
  name: "Bob",
  age: 25,
};

const { name: userName, age: userAge } = person;

greet(person); // 'Hello, Bob! You are 25 years old.'
console.log(`객체 직접 참조 : ${person.name}`);
console.log(`객체 구조 분해 할당 : ${userName}`);

2-5. 클래스

class Car {
  constructor(modelName, modelYear, type, price) {
    this.modelName = modelName;
    this.modelYear = modelYear;
    this.type = type;
    this.price = price;
  }
  getModelName() {
    return this.modelName;
  }
  getModelYear() {
    return this.modelYear;
  }
  getType() {
    return this.type;
  }
}

// const car = new Car({"rullu", 2024, "car", 24000});
// Car 클래스를 사용하여 인스턴스 생성
const myCar = new Car("Toyota", 2022, "SUV", 30000);

// 메서드를 호출하여 속성에 접근
console.log(myCar.getModelName()); // 'Toyota'
console.log(myCar.getModelYear()); // 2022
console.log(myCar.getType()); // 'SUV'
  • 클래스 사용한 객체 사용 예제
class User {
  constructor(username, email) {
    this.username = username;
    this.email = email;
    this.isLoggedIn = false;
  }

  login() {
    // 로그인 처리 로직
    this.isLoggedIn = true;
    console.log(`${this.username}님이 로그인했습니다.`);
  }

  logout() {
    // 로그아웃 처리 로직
    this.isLoggedIn = false;
    console.log(`${this.username}님이 로그아웃했습니다.`);
  }

  displayUserInfo() {
    console.log(`Username: ${this.username}, Email: ${this.email}`);
  }
}

// User 클래스를 사용하여 객체 생성
const user1 = new User("Alice", "alice@example.com");
const user2 = new User("Bob", "bob@example.com");

// 객체의 메서드 호출
user1.displayUserInfo(); // 출력: Username: Alice, Email: alice@example.com
user2.displayUserInfo(); // 출력: Username: Bob, Email: bob@example.com

// 로그인 기능 사용
user1.login(); // 출력: Alice님이 로그인했습니다.
console.log(user1.isLoggedIn); // 출력: true

console.log("로그아웃 중");

// 로그아웃 기능 사용
setTimeout(() => {
  user1.logout(); // 출력: Alice님이 로그아웃했습니다.
  console.log(user1.isLoggedIn); // 출력: false
}, 2000);

 

2-6. 콜백함수

파라미터로 받은 함수를 호출하면 → 콜백함수

요청한 일이 끝난 후에 실행되는 함수

비동기 함수와 함께 사용되는 주요한 개념

즉 함수를 파라미터로 전달하는 것

 

오래걸리는 작업이 완료되는 것을 확인, 완료 후 할 일 정해주기

함수를 다른 함수의 파라미터로 전달하여 나중에 실행되도록 하는 것이 바로 콜백 함수입니다. 이것이 콜백 함수가 하는 역할 중 하나에요. 함수를 파라미터로 전달하여 원하는 시점에 실행되도록 할 수 있습니다.

일반적으로 콜백 함수는 비동기 작업이 완료되었을 때 실행되도록 사용됩니다. 함수가 비동기적으로 동작할 때는 결과를 바로 반환하지 않고, 작업이 완료되면 콜백 함수를 호출하여 결과를 전달합니다. 이렇게 하면 코드의 흐름이 중단되지 않고 프로그램이 동시에 여러 작업을 처리할 수 있습니다.

function asyncFunction(param1, param2, callback) {
  console.log("1. start > asyncFunction()");
  setTimeout(() => {
    let sum = param1 + param2;
    callback(sum);
  }, 1000);
  console.log("2. end > asyncFunction()");
}

asyncFunction(3, 4, (result) => {
  console.log("4. Result:", result);
});

console.log("3. After asyncFunction() called");

// 1. 웹 애플리케이션에서 AJAX 요청을 보내고, 서버에서 데이터를 받은 후에 UI를 업데이트하는 경우.
function fetchDataAndUpdateUI(callback) {
  // AJAX 요청 보내는 코드
  // 서버로부터 데이터를 받아오는 비동기 작업
  setTimeout(() => {
    const dataFromServer = { message: "Hello, world!" };
    callback(dataFromServer); // 받아온 데이터를 콜백 함수에 전달
  }, 1000); // 1초 후에 콜백 함수 호출
}

// 2. Node.js에서 파일을 읽거나 쓸 때 콜백 함수를 사용하여 파일 작업이 완료된 후에 다음 작업을 수행하는 경우.
function readFileAndProcess(callback) {
  // 파일을 읽는 코드
  fs.readFile("example.txt", "utf8", (err, data) => {
    if (err) {
      console.error("Error reading file:", err);
      return;
    }
    callback(data); // 읽은 데이터를 콜백 함수에 전달
  });
}

// 3. setTimeout 또는 setInterval과 같은 타이머 함수를 사용하여 특정 시간이 지난 후에 작업을 수행하는 경우.
function performDelayedTask(callback) {
  // 일정 시간이 지난 후에 실행할 작업을 처리하는 코드
  setTimeout(() => {
    console.log("Performing delayed task...");
    callback(); // 지정된 시간이 지난 후에 콜백 함수 호출
  }, 2000); // 2초 후에 콜백 함수 호출
}

// 4. 이벤트 핸들러를 등록할 때 콜백 함수를 사용하여 이벤트가 발생했을 때 실행할 코드를 지정하는 경우.
function registerEventHandlerAndCallback(callback) {
  // 이벤트 핸들러 등록 코드
  document.getElementById("myButton").addEventListener("click", callback);
}

// 실제 사용 예시
fetchDataAndUpdateUI((data) => {
  console.log("Received data from server:", data.message);
  // UI 업데이트 작업 수행
});

readFileAndProcess((data) => {
  console.log("File content:", data);
  // 파일 데이터를 이용한 추가 작업 수행
});

performDelayedTask(() => {
  console.log("Delayed task completed!");
  // 추가 작업 수행
});

registerEventHandlerAndCallback(() => {
  console.log("Button clicked!");
  // 추가 작업 수행
});
# 2. 자바스크립트 기본

3. Node.js 기본 및 활용

모듈

  • 재사용
  • modules.export{ ~~ }
  • require문 → import 문
  • 내장 모듈과 객체

4. Node.js로 웹서버 구축하기

간단 서버 실습1 - 클라이언트로부터의 요청 url 확인하기

// 서버 사용을 위해 http 모듈을 불러와 http 변수에 할당한다.
const http = require("http");

// http 모듈로 서버를 생성한다.
let server = http.createServer(function (req, res) {
  // 클라이언트 요청의 URL과 메서드를 출력
  console.log("Request URL:", req.url);
  console.log("req 뭐있냐: ", req.headers);

	// 응답 객체 헤더 생성 및 종료
	// 상태 코드 200(성공), 응답 타입 text/html
  res.writeHead(200, { "Content-Type": "text/html" });
  res.end("Hello World!");
});

// listen 함수로 8080 포트를 가진 서버를 실행
server.listen(8080, function () {
  console.log("Server is running on <http://localhost:8080>");
});

요청 url
콘솔 출력

간단 서버 실습2 - GET 요청 받고 처리하기

var http = require("http"); // url 사용
var url = require("url"); // querystring 사용
var querystring = require("querystring"); // 서버 생성

var server = http.createServer(function (req, res) {
  console.log("---- start ----"); 

  var parsedUrl = url.parse(req.url); // url 구성요소 파싱
  console.log(parsedUrl); 

  var parsedQuery = querystring.parse(parsedUrl.query, "&", "=");
  console.log(parsedQuery);

  console.log("---- end ----");

  res.writeHead(200, { "Content-Type": "text/html" });
  res.end("Hello node.js!");
});

server.listen(8090, function () {
  console.log("Server is running...");
});

이 코드는 Node.js의 기본 모듈인 http, url, 그리고 **querystring**을 사용하여 간단한 웹 서버를 만들고, 클라이언트로부터 받은 URL을 파싱하여 쿼리 스트링 부분을 객체화하여 출력하는 예제입니다.

var http = require("http"); // url 사용
var url = require("url"); // querystring 사용
var querystring = require("querystring"); // 서버 생성

var server = http.createServer(function (req, res) {
  console.log("---- start ----");

  var parsedUrl = url.parse(req.url); // url.parse()로 url 파싱(객체화)
  console.log(parsedUrl);

  // querystring.parse()로 url 객체의 query 속성을 키-값 쌍의 객체로 변환
  var parsedQuery = querystring.parse(parsedUrl.query, "&", "=");
  console.log(parsedQuery);

  console.log("---- end ----");

  res.writeHead(200, { "Content-Type": "text/html" });
  let title = "HOME";
  if (parsedUrl.pathname == "/about") {
    title = "ABOUT";
  } else if (parsedUrl.pathname == "/board") {
    title = "BOARD";
  }
  res.end(`
  <!DOCTYPE html>
  <html>
  <head>
  <title>${title}</title>
  </head>
  <body>
  <h1>This is ${title}</h1>
  </body>
  </html>  
  `);
});

server.listen(8090, function () {
  console.log("Server is running...");
});

각 url path에 대해 html 값 넣기

var http = require("http"); // url 사용
var url = require("url"); // querystring 사용
var querystring = require("querystring"); // 서버 생성

var server = http.createServer(function (req, res) {
  console.log("---- start ----");

  var parsedUrl = url.parse(req.url); // url.parse()로 url 파싱(객체화)
  console.log(parsedUrl);

  // querystring.parse()로 url 객체의 query 속성을 키-값 쌍의 객체로 변환
  var parsedQuery = querystring.parse(parsedUrl.query, "&", "=");
  console.log(parsedQuery);

  console.log("---- end ----");

  res.writeHead(200, { "Content-Type": "application/json" });
  let title = "HOME";
  if (parsedUrl.pathname == "/about") {
    title = "ABOUT";
  } else if (parsedUrl.pathname == "/board") {
    title = "BOARD";
  }
  res.end(JSON.stringify(`{'title': ${title}}`));
});

server.listen(8090, function () {
  console.log("Server is running...");
});

json으로 데이터 전송하기

var http = require("http"); // url 사용
var url = require("url"); // querystring 사용
var querystring = require("querystring"); // 서버 생성

var server = http.createServer(function (req, res) {
  console.log("---- start ----");

  var parsedUrl = url.parse(req.url); // url.parse()로 url 파싱(객체화)
  console.log(parsedUrl);

  // querystring.parse()로 url 객체의 query 속성을 키-값 쌍의 객체로 변환
  var parsedQuery = querystring.parse(parsedUrl.query, "&", "=");

  console.log("---- end ----");

  res.writeHead(200, { "Content-Type": "text/html" });
  let title = "Home";

  if (parsedUrl.pathname == "/about") {
    title = "About";
  } else if (parsedUrl.pathname == "/board") {
    title = "Board";
  } else if (parsedUrl.pathname == "/users") {
    title = "Users";
  }

  let userName = "";
  let userAge = "";

  if (parsedUrl.pathname == "/users") {
    userName = parsedQuery.name || "unknown";
    userAge = parsedQuery.age || "unknown";
  } else if (parsedQuery.name === "jeongwon") {
    userName = "jeongwon";
  } else {
    userName = "unknown";
  }

  let responseBody = `
  <!DOCTYPE html>
  <html>
  <head>
  <title>${title}</title>
  </head>
  <body>
  <h1>This is ${title}</h1>
  <h3>My name is ${userName}</h3>
  `;

  if (parsedUrl.pathname == "/users") {
    responseBody += `<h3>My age is ${userAge}</h3>`;
  }

  responseBody += `
  </body>
  </html>  
  `;

  res.end(responseBody);
});

server.listen(8090, function () {
  console.log("Server is running...");
});

// 익명 함수로 바꾸고 콜백 넣기

const http = require("http"); // url 사용
const url = require("url"); // querystring 사용
const querystring = require("querystring"); // 서버 생성

const serverHandler = (req, res) => {
  console.log("---- start ----");

  const parsedUrl = url.parse(req.url); // url.parse()로 url 파싱(객체화)
  console.log(parsedUrl);

  // querystring.parse()로 url 객체의 query 속성을 키-값 쌍의 객체로 변환
  const parsedQuery = querystring.parse(parsedUrl.query, "&", "=");

  console.log("---- end ----");

  res.writeHead(200, { "Content-Type": "text/html" });
  let title = "Home";

  if (parsedUrl.pathname == "/about") {
    title = "About";
  } else if (parsedUrl.pathname == "/board") {
    title = "Board";
  } else if (parsedUrl.pathname == "/users") {
    title = "Users";
  }

  let userName = "";
  let userAge = "";

  if (parsedUrl.pathname == "/users") {
    userName = parsedQuery.name || "unknown";
    userAge = parsedQuery.age || "unknown";
  } else if (parsedQuery.name === "jeongwon") {
    userName = "jeongwon";
  } else {
    userName = "unknown";
  }

  let responseBody = `
    <!DOCTYPE html>
    <html>
    <head>
    <title>${title}</title>
    </head>
    <body>
    <h1>This is ${title}</h1>
    <h3>My name is ${userName}</h3>
    `;

  if (parsedUrl.pathname == "/users") {
    responseBody += `<h3>My age is ${userAge}</h3>`;
  }

  responseBody += `
    </body>
    </html>  
    `;

  res.end(responseBody);
};

const listenHandler = () => {
  console.log("Server is running...");
};

const server = http.createServer(serverHandler);
server.listen(8090, listenHandler);

간단 서버 실습3 - POST 요청 처리

const http = require("http");
const querystring = require("querystring");

const serverHandler = (req, res) => {
  const postdata = "";
  req.on("data", (data) => {
    postdata = postdata + data;
  });
  req.on("end", () => {
    const parsedQuery = querystring.parse(postdata);
    console.log(parsedQuery);
    res.writeHead(200, { "Content-Type": "text/html" });
    res.end("keyName1=" + parsedQuery.keyName1);
  });
};

const listenHandler = () => {
  console.log("Server is running...");
};

const server = http.createServer(serverHandler);
server.listen(8070, listenHandler);