(41)

[NestJS] Docker와 Git Actions, EC2를 활용한 CI/CD 환경 구축 - 도커라이징하여 EC2에 배포하기

서론 사내 서비스가 Git Actions, Docker, EC2, PM2를 활용해 CI/CD 및 파이프라인을 구축하여 사용하고 있었기에 학습이 필요하다.. 간단하게 따라해볼 무언가를 찾아서 따라해보면서 학습할 것이 필요했는데 초행이다보니 같은 환경에서 진행한 분을 찾기 어려워 외국 유튜브 영상이나 기타 해외 자료들을 활용하며 따라하며 공부했다. CI/CD란? Continuos Integration / Continuous Delivery의 약자로 단어 뜻대로 지속적 통합과 배포를 뜻한다. DockerFile 작성 Docker에 이미지를 빌드하고 컨테이너 생성을 하는 과정은 단순하고 Nodejs docs에서도 가이드라인을 제시해준다. https://nodejs.org/ko/docs/guides/nodejs-..

[Javascript] using : 자바스크립트의 새로운 변수 키워드

서론 즐겨보는 개발 관련 유튜브 중 하나인 노마드코더에서 흥미로운 영상을 게시했다. 기존 자바스크립트의 변수 const, var, let을 대체할 강력한 키워드인 using 이라고 소개했다. 역시 프로유튜버답게(??) 어그로를 잘 끄셔서 자연스레 정주행했다. 현재 자바에서 타입스크립트로 전향한 지금, 꽤나 중요한 이슈사항이 될 것 같아서 포스팅하여 정리해보려고 한다. https://www.youtube.com/watch?v=-NmwyJ5S-IY&t=151s 기존의 자바스크립트 변수 간단하게 기존 변수들에 대해 알아보자. es6에서 포함된 기능중 하나는, 변수 선언에 사용할 수 있는 키워드인 let, const의 추가였다. 혹여 단순 using에 대한 정보만을 얻고자한다면, 스크롤을 많이 내려야 할 것 같..

[NestJS] JWT Guard 사용 중 에러 (metatype is not a constructor / In order to use "defaultStrategy", please, ensure to import PassportModule in each place where AuthGuard() is being used, Otherwise, passport won't work correctly.)

에러 메세지 JWT Guard설정 후 테스트를 위해 서버를 실행했을 때 발생 원인 그대로 번역하면 메타타입은 생성자가 아니라는 것 같음. metatype is not a constructor에러는 커스텀 가드를 사용할 때 발생할 수 있는 일반적 오류중 하나이며 @UseGuards() 데코레이터에 가드 클래스를 전달할 때 메타타입 정보가 올바르게 전달되지 않을 때 발생한다고 한다. 즉, 인스턴스를 전달하는 것이 아니라 생성자 함수를 전달해야한다. //before @UseGuards(AuthGuard) //after @UseGuards(AuthGuard()) 문제가 된 부분을 수정해주었음. 새로운 에러 In order to use "defaultStrategy", please, ensure to import..

[NestJS] NestJS 개발 환경 셋팅

서론 9개월 정도를 스프링만 사용하던 신입 개발자인 나에게 갑작스레 실무에서 Node.js를 사용해야하는 상황이 닥쳤다. 기존 backend 코드를 보니 모듈을 import하는 것들이 대부분 @nestJS/어쩌고로 되어있는 typescript였다. 실무를 위해 빠른 적응이 필요했다. 나중에 node와 typescript, nest등에 대한 적당히 자세한(?) 공부 및 포스팅을 진행할 것이다. 간단하게 알아본 바로는 다음과 같았다. NestJS는 서버 측 노드 애플리케이션을 구축하기 위한 프레임워크로 Express와 같은 HTTP Server Framework를 내장하고 있고, 대부분 TypeScript로 구성되어 있다고한다. 이미 셋팅되어있는 개발환경을 다시 뜯어보기전에, 어떻게 개발환경을 셋팅하는지 부..

자주쓰는 npm 명령어 정리 (npx)

명령어 정리 npx npm 5.2.0버전부터 제공되는 명령어로 npx를 사용하면 install 없이 일회성으로 패키지를 실행하거나 명령을 실행할 수 있다. 1. npm init - package.json 생성 2. npm install / npm i - npm 모듈을 로컬(현재 프로젝트 내부)에 설치 - 옵션으로 --save / -S, --save-dev / -D를 사용 가능 - --save / -S는 dependency에 추가하는 명령어인데 npm 5버전 이후로는 자동으로 dependency에 추가됨 - --save-dev / -D는 devDependencies에 추가. 개발 환경에서만 사용하는 모듈 3. npm uninstall moduleName - 설치한 모듈 삭제 4. npm dedupe - 중..

[백준 10845 / Java] 큐

문제 링크 https://www.acmicpc.net/problem/10845 10845번: 큐 첫째 줄에 주어지는 명령의 수 N (1 ≤ N ≤ 10,000)이 주어진다. 둘째 줄부터 N개의 줄에는 명령이 하나씩 주어진다. 주어지는 정수는 1보다 크거나 같고, 100,000보다 작거나 같다. 문제에 나와있지 www.acmicpc.net 풀이 큐 포스팅 링크 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; public class Main ..

[JavaScript] 식사 메뉴를 선정을 위한 돌림판(룰렛) 만들기

결정장애라 메뉴선정시에 항상 어려움을 겪곤 한다. 학원 수강 중 메뉴 선정에 대한 고민을 하였고, 결국 오픈소스를 활용하여 돌림판 제작을 시작했다. See the Pen Untitled by magic (@mag11c) on CodePen. 자주가는 식당들을 product 배열에 미리 추가한 뒤, 해당 배열의 길이와 동일하게 색상 배열을 만들어 색상을 미리 추가시켰다. 그리고 새로운 메뉴를 추가할 수 있다. const $c = document.querySelector("canvas"); const ctx = $c.getContext(`2d`); const menuAdd = document.querySelector('#menuAdd'); const product = ["햄버거", "순대국", "정식당", ..

[JavaScript] localStorage를 활용한 todoList 만들기

HTML todos Left click to toggle completed. Right click to delete todo input에서 입력된 값을 ul의 li에다가 담을 것이며, 스토리지를 이용해 새로고침해도 리스트가 남아있게 할 예정이다. 자바스크립트 공부를 위함이니 css는 맨 아래에다가 작성 해 두겠다. JS 새로고침 이후에도 유지되기 위해 스토리지를 활용할 것이다. localStorage에 데이터를 넘겨주기 위해 배열을 JSON.stringify() 메서드로 포맷팅한 뒤 넘겨주었다. const form = document.querySelector('form'); const input = document.querySelector('input'); const ul = document.queryS..

[JavaScript] 웹 스토리지(Web Storage)

웹 스토리지 ( Web Storage ) 기존 쿠키의 문제점을 보완하기 위해 사용한다. https://mag1c.tistory.com/187 쿠키와 세션 (Cookie & Session) HTTP 프로토콜의 특징 비연결성 ( Connectionless ) 클라이언트가 서버에 요청(Request)할 때, 그에 대한 응답(Response)을 한 후, 연결을 끊는다. 비상태성 ( Stateless ) 클라이언트의 상태 정보를 가지지 않는 mag1c.tistory.com 웹 스토리지의 특징 1. 브라우저 내부에 Key, Value 쌍을 저장하는 공간이다. 2. 네트워크 요청 시 서버로 전송되지 쿠키에 비해 않아 많은 자료보관이 가능하다. (2MB이상) 3. 서버가 HTTP 헤더를 통해 스토리지 객체를 조작할 수..

[NestJS] Docker와 Git Actions, EC2를 활용한 CI/CD 환경 구축 - 도커라이징하여 EC2에 배포하기

Tech/JavaScript & TypeScript 2023. 9. 22. 14:43
728x90

 

 

서론

사내 서비스가 Git Actions, Docker, EC2, PM2를 활용해 CI/CD 및 파이프라인을 구축하여 사용하고 있었기에 학습이 필요하다..

 

간단하게 따라해볼 무언가를 찾아서 따라해보면서 학습할 것이 필요했는데

 

초행이다보니 같은 환경에서 진행한 분을 찾기 어려워 외국 유튜브 영상이나 기타 해외 자료들을 활용하며 따라하며 공부했다.

 

 

 

 

CI/CD란?

Continuos Integration / Continuous Delivery의 약자로 단어 뜻대로 지속적 통합과 배포를 뜻한다.

 

 

 

 

 

DockerFile 작성

Docker에 이미지를 빌드하고 컨테이너 생성을 하는 과정은 단순하고 Nodejs docs에서도 가이드라인을 제시해준다.

https://nodejs.org/ko/docs/guides/nodejs-docker-webapp

 

Node.js 웹 앱의 도커라이징 | Node.js

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.

nodejs.org

하지만 netsjs dockerfile을 사용하고 싶어 검색하여 사용하였다..

https://www.tomray.dev/nestjs-docker-production

 

Ultimate Guide: NestJS Dockerfile For Production [2022]

Learn how to write a Dockerfile that creates a production optimized image using the NodeJS Alpine image and multistage builds.

www.tomray.dev

 

나는 예제에다 alpine을 추가하여 사용했는데, 알파인은 클라우드 환경을 고려한 가벼운 리눅스 이미지다.

 

실습을 진행할 것이기 때문에 알파인으로 사용하였다.

DockerFile

# Base image
FROM node:18-alpine

# Create app directory
WORKDIR /usr/src/app

# A wildcard is used to ensure both package.json AND package-lock.json are copied
COPY package*.json ./

# Install app dependencies
RUN npm install

# Bundle app source
COPY . .

# Creates a "dist" folder with the production build
RUN npm run build

# Start the server using the production build
CMD [ "node", "dist/main.js" ]

 

.dockerignore

Dockerfile
.dockerignore
node_modules
npm-debug.log
dist

 

 

CI를 위한 설정파일 생성

github actions은 CI를 역할을 수행하는 강력한 도구중 하나이다.

github actions을 사용하기 위해 깃헙 내 레포에서 Actions에서 워크플로우를 생성했다.

git-action.yml

name: Docker Image CI

on:
  push:
    branches: [ "main" ]

jobs:

  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3
    - name: Login Dockerhub
      env:
        DOCKER_USERNAME: ${{secrets.DOCKERHUB_USERNAME}}
        DOCKER_PASSWORD: ${{secrets.DOCKERHUB_PASSWORD}}
      run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD      

    - name: Build the Docker image
      run: docker build -t manaweb-api .
    - name: taging
      run: docker tag manaweb-api:latest mag123c/manaweb-api:latest
    - name: Push to Dockerhub
      run: docker push mag123c/manaweb-api:latest

 

1. 메인 브렌치에 push될 때만 동작

2. 현재 레포의 코드를 체크아웃

3. 환경변수를 통한 도커 로그인

4. 도커 이미지 빌드

5. 푸시

 

환경변수는 내 깃허브 레포 > Secrets > Secrets and variable에서 변수 설정을 할 수 있다.

 

또한 여기서 푸시와 빌드 사이에  이미지 태깅에 대한 코드를 추가하였는데, 아래 트러블슈팅 1에서 다루었듯이 빌드 후 바로 푸시를 하게 되면 올바른 태그네임을 찾을 수 없다는 에러를 뱉어냈다.

 

 

 

Github Actions 동작 확인하기

계속된 실패로 지쳐있다가 태깅을 해주자 성공적으로 작동하는 모습이다.

 

 

 

AWS EC2 생성 및 Github Runner(self-hosted) 생성

ubuntu의 프리티어를 사용했으며 간단하게 인바운드 설정만 마친 뒤 인스턴스를 생성했다.

생성에 대한 포스팅은 생략하도록 하며 접속이 완료된 후 아래 명령어를 실행하자.

# root 계정으로 전환 + 현재 환경변수 사용
sudo su

# 패키지 버전 최신화
sudo apt update
sudo apt-get upgrade -y

 

깃허브의 레포에서 Settings - Actions - runner - New self-hosted runner에서 셋팅에 관련된 가이드라인을 받아볼 수 있다.

 

self-hosted의 가이드라인을 따라할 뿐이지만, 코드의 대략적인 기능을 파악해보았다.

#Download
## 1. 디렉토리 생성 및 이동
mkdir actions-runner && cd actions-runner
## 2. actions-runner 설치
curl -o actions-runner-linux-x64-2.309.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.309.0/actions-runner-linux-x64-2.309.0.tar.gz
## 3.설치한 파일의 SHA-256 해시를 계산하여 파일 체크
echo "2974243bab2a282349ac833475d241d5273605d3628f0685bd07fb5530f9bb1a  actions-runner-linux-x64-2.309.0.tar.gz" | shasum -a 256 -c
## 4.설치
tar xzf ./actions-runner-linux-x64-2.309.0.tar.gz

####################################################

#Configure
## 1. 내 레포와 연결. 액세스 권한이 있는 상태로 설정됨
./config.sh --url https://github.com/mag123c/manaweb-api --token A4Y6RDGK6BFAYHTP2KKLL2LFAXJFC
## 2. 러너 실행
./run.sh
### ./run.sh & : 백그라운드로 실행
### nohup ./run.sh : 세션이 종료되어도 runner 유지 (ssh 세션 끊어도 유지)

정상적으로 ubuntu ip의 Runners가 등록된 모습

 

마지막 명령어인 ./run.sh를 사용하여 실행시키면 아래와 같은 모습이 된다.

 

 

 

CD pipeline 생성

workflows에 CD를 위한 yml파일을 생성했다.

name: CD Pipeline

on:
  workflow_run:
    workflows: ["CI Pipeline"]
    types:
        - completed

jobs:

  build:

    runs-on: self-hosted

    steps:
    - name: Pull Docker image
      run: sudo docker pull mag123c/manaweb-api:latest
    - name: Delete Old docker container
      run: sudo docker rm -f manaweb-api-container || true
    - name: Run Docker Container
      run: sudo docker run -d -p 8080:8080 --name manaweb-api-container mag123c/manaweb-api

 

1. github actions에서 CI pipline이 동작이 완료되었을 때 CD 파이프라인이 실행되게 이름 및 타입 설정.

on:
  workflow_run:
    workflows: ["CI Pipeline"]
    types:
        - completed

 

 

2. runs-on은 작업이 실행될 환경을 지정한다. 깃헙 가이드라인에 따르면, yaml 파일에 self-hosted를 붙이라고 되어 있다.

 

3. 이미지를 다운로드(Pull)하고, 이전 컨테이너를 삭제한 다음 새로운 컨테이너를 실행한다.

 

CD 파이프라인은 CI 파이프라인이 성공적으로 동작이 완료되면 Docker 이미지를 다운로드하고 이를 사용하여 새로운 컨테이너를 실행하여 배포하는 역할을 한다. 이전 버전의 컨테이너는 삭제되고 새로운 버전이 설정한 포트를 통해 실행될 것이다.

 

 

 

EC2에 Docker 설치

https://docs.docker.com/engine/install/ubuntu/

 

Install Docker Engine on Ubuntu

Jumpstart your client-side server applications with Docker Engine on Ubuntu. This guide details prerequisites and multiple methods to install.

docs.docker.com

 

위 공식문서를 활용하여 설치를 진행해보자.

# Docker 공식 GPG Key 추가
## 1. 패키지 목록 업데이트
sudo apt-get update
## 2. 필요한 패키지 설치(인증, 통신)
sudo apt-get install ca-certificates curl gnupg
## 3. Docker 패키지 관리에 사용할 dir 생성
sudo install -m 0755 -d /etc/apt/keyrings
## 4. GPG Key 다운로드 + 파일 경로 및 파일명 지정
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# Docker Repo를 Apt 소스에 추가
## 1. Apt 저장소를 /etc/apt/sources.list.d/docker.list 파일에 추가
## 현재 ubuntu 버전의 코드명을 가져와서 사용
echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
## 2. Docker 패키지 사용을 위한 업데이트
sudo apt-get update
## 3. Docker 관련 패키지 설치 (도커엔진, 플러그인, compose 등)
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

 

설치가 완료되었으면 확인해보자.

# 현재 실행중인 Docker Container 목록 표시
sudo docker ps

정상적으로 찍히는 모습이다. (아직 실행중인 컨테이너가 없기 때문에 아무 값도 없다)

이후 docker login 명령어를 통해 도커에 로그인을 한다.

 

 

 

EC2에서 컨테이너 확인

Git Actions을 통해 CI/CD가 완료되고 나면 EC2에 정상적으로 컨테이너가 실행이 되어야 한다.

# 실행중인 docker container 확인
docker ps

하지만 명령어를 입력해도 실행되는것이 없어 5~6시간을 정상적으로 EC2에 배포가 되지 않은 것으로 착각했다.

 

# 실행중이거나 종료된 모든 컨테이너 확인
docker ps -a

모든 컨테이너를 확인해보니 아래처럼 컨테이너를 실행하자 마자 종료가 되는 모습이다.

해결한 과정은 아래 트러블슈팅의 2번 항목에서 확인할 수 있다.

 

 

5~6시간의 삽질 끝에 에러를 해결하고 나니 정상적으로 컨테이너가 실행되었다

 

curl 명령어를 통해 확인해보면 잘 동작한다.

또한 ec2의 public ip:port로 URL을 입력해도 잘 동작하는 모습.

 

 

이제 배포된 프로젝트의 API를 프론트에서 호출하여 사용하기만하면 끝!

 

 

 

 

 

 

트러블 슈팅

1. An image does not exist locally with the tag: [repo]/[image]

https://mag1c.tistory.com/466

 

[Git Actions / Docker] An image does not exist locally with the tag: [repo]/[image]

에러 메세지 An image does not exist locally with the tag: [repo]/[image] 아래와 같은 action.yml을 사용하고있었는데 계속해서 리파지토리에 이미지를 빌드한 후 태그를 못잡아 주는 것 같았다. name: Docker Image CI

mag1c.tistory.com

 

 

2. Docker Container 실행 후 바로 종료되는 현상

https://mag1c.tistory.com/469

 

 

 

 

참조

1. AWS EC2에 도커 설치하려다 에러 발생했을 때

https://boying-blog.tistory.com/82

 

ubuntu docker 설치시 Package 'docker-ce' has no installation candidate 해결

오랜만에 다른 서버에 도커를 설치할 일이 생겼는데 새 서버다 보니 이런 오류를 맞이했습니다 허허 Package 'docker-ce' has no installation candidate docker-ce 패키지를 사용할 수 없습니다. 하지만 다른 패

boying-blog.tistory.com

ssh를 password없이 restart할 때

$ sudo /etc/init.d/ssh restart

 

2. 'github action run after another' 키워드로 검색

https://stackoverflow.com/questions/62750603/github-actions-trigger-another-action-after-one-action-is-completed

 

Github Actions - trigger another action after one action is completed

I have one action (a yaml file) for deploying a docker image to Google Cloud Run. I would like to receive Slack or Email messages informing the build and push results. How could the message action be

stackoverflow.com

 

 

3. 궁금해서 찾아본 workflow 트리거들

https://docs.github.com/en/actions/using-workflows/triggering-a-workflow

 

Triggering a workflow - GitHub Docs

How to automatically trigger GitHub Actions workflows

docs.github.com

 

 

4. ubuntu에 docker 설치하기 (docker docs)

https://docs.docker.com/engine/install/ubuntu/

 

Install Docker Engine on Ubuntu

Jumpstart your client-side server applications with Docker Engine on Ubuntu. This guide details prerequisites and multiple methods to install.

docs.docker.com

 

300x250
mag1c

mag1c

2년차 주니어 개발자.

[Javascript] using : 자바스크립트의 새로운 변수 키워드

Tech/JavaScript & TypeScript 2023. 9. 18. 19:09
728x90

 

서론

즐겨보는 개발 관련 유튜브 중 하나인 노마드코더에서 흥미로운 영상을 게시했다.

 

기존 자바스크립트의 변수 const, var, let을 대체할 강력한 키워드인 using 이라고 소개했다.

역시 프로유튜버답게(??) 어그로를 잘 끄셔서 자연스레 정주행했다.

현재 자바에서 타입스크립트로 전향한 지금, 꽤나 중요한 이슈사항이 될 것 같아서 포스팅하여 정리해보려고 한다.

 

https://www.youtube.com/watch?v=-NmwyJ5S-IY&t=151s 

 

 

기존의 자바스크립트 변수

간단하게 기존 변수들에 대해 알아보자.

es6에서 포함된 기능중 하나는, 변수 선언에 사용할 수 있는 키워드인 let, const의 추가였다.

혹여 단순 using에 대한 정보만을 얻고자한다면, 스크롤을 많이 내려야 할 것 같다.

 

 

var의 문제점

기존의 var 변수가 함수 외부에서 선언될 때의 범위는 전역이고, 함수 내에서 선언될 때는 함수 범위 내로 지정된다.

var hi = "hey hi";

function test() {
    var hello = "hello";
}

//error: hello is not found
console.log(hello);

hello는 test() 함수 밖에서 사용할 수 없기 때문에 에러가 발생할 것이다.

 

또한 var 변수는 재선언이 가능하다.

var test = "test code"
var test = "test test"
var test2 = "test2 code"
test2 = "test2 test2"

 

var는 호이스팅 때문에 다음과 같은 코드를 작성하면 test is undefined를 발생시킨다.

console.log(test)
var test = "test"
//** test is undefined!!!!

//real action (Hoisting)
var test
console.log(test)
test = "test"

 

var의 이와같은 특징들을 활용하여 아래 코드를 살펴보자.

얼핏보면 괜찮은 var을 활용한 코드를 작성했다고 생각할 수 있다.

var test = "test"
var test2 = 10

if (test > 9) {
    var test = "hi hello"
}

console.log(test)
// >> "hi hello"

위 코드는 if문이 true기 때문에 test 변수는 재정의된다. 의도적으로 재정의한다면 괜찮겠지만, test 변수가 이미 정의되어 있다는 사실을 인식하지 못하는 경우에는 문제가 될 수 있다. 코드의 다른 부분에서 test 변수를 사용했다면 다른 출력값에 당황하게 될 수 있다.

 

요약해보면, var은 블록 스코프를 지원하지 않고, 재선언이 가능한 특성을 갖고 있다. 또한 호이스팅이 발생하기 때문에 가독성이 저하되고, 변수 선언 이전에 변수를 사용해도 오류가 발생하지 않는 등 많은 문제를 발생시킨다. 그리하여 const, let이 등장하게 되었다.

 

 

let

let으로 선언된 변수는 해당 블록 내에서만 사용이 가능하다.

let test = 'test'
let test2 = 10

if (test2 > 9) {
    let result = 'let is powerful'
    console.log(result) // 'let is powerful'
}
console.log(result) // result is not defined

result 변수가 정의된 블록 외부에서 해당 변수를 사용하면 에러가 발생된다.

 

또한 let은 업데이트는 가능하지만, 재선언은 불가능하다.

let test = 'test'
test = 'not test, its real'
let test = 'test'
let test = 'not test, its real'
// error: Identifier 'test' has already been declared

 

하지만, 다른 범위 내에서 재정의된다면 에러는 발생하지 않는다. 서로 다른범위를 가지므로 서로 다른 변수로 취급되기 때문이다.

let test = 'test'

if (true) {
    let test = 'its real test'
}
console.log(test) // 'its real test'

 

이처럼 let을 사용한다면, 변수가 범위 내에서만 존재하기 때문에, 이전에 이미 사용한 변수 명에 대해 더이상 신경쓰지 않아도 된다. 또한 범위 내에서 동일한 변수를 두 번 이상 선언할 수 없기 때문에 var의 문제가 발생하지 않는다.

 

또한 let의 경우, 호이스팅 시 초기화가 되지 않는다. 즉 호이스팅으로 선언은 되었지만 초기화가 되지 않았으므로 undefined의 값을 가지지 못한다. 그리하여 Reference Error를 뱉어낸다. test가 초기화되기 전에 사용되었기 때문이다.

console.log(test)
let test = "test"

//real action
let test
console.log(test) // Reference Error
test = "test"

 

여기서 계속 말하는 선언과 초기화는 다음 정의를 가진다.
선언(Declaration) : 스코프와 변수 객체 생성. 스코프가 변수 객체를 참조
초기화(Initialization) : 변수 객체 값을 위한 공간을 메모리에 할당된다 (undefined)

 

 

 

const

단어의 뜻에서도 알 수 있듯이 상수값을 유지하는 변수이다. 당연히 업데이트도, 재선언도 불가능하다.

const 또한 let처럼 선언된 블록 범위에서만 접근이 가능하다.

const test = 'i am constants'
test = 'change' //error: Assignment to constant variable
const test = 'i am constants'
const test = 'change' // error: Identifier 'test' has already been declared

 

하지만 객체의 프로퍼티의 "값"은 변경이 가능하다.

const test = {
    name: 'constants'
    age: 1
}

test.name = 'change'

 

const의 호이스팅도 let과 마찬가지다.

 

 

요약

구분 var let const
범위 전역 블록 범위 블록 범위
재할당 O O X
재선언 O X X
호이스팅 시 초기화여부 O X X
선언과 초기화 초기화없이 선언 가능 초기화없이 선언 가능 선언단계에서 초기화 필수

 

 

참조

https://www.youtube.com/watch?v=-NmwyJ5S-IY&t=151s 

https://letsusetech.com/introducing-javascripts-new-using-keyword-for-variables

https://www.freecodecamp.org/korean/news/var-let-constyi-caijeomeun/

 

 

 

 

300x250
mag1c

mag1c

2년차 주니어 개발자.

[NestJS] JWT Guard 사용 중 에러 (metatype is not a constructor / In order to use "defaultStrategy", please, ensure to import PassportModule in each place where AuthGuard() is being used, Otherwise, passport won't work correctly.)

Tech/JavaScript & TypeScript 2023. 9. 10. 22:30
728x90

 

 

에러 메세지

JWT Guard설정 후 테스트를 위해 서버를 실행했을 때 발생

 

 

 

원인

그대로 번역하면 메타타입은 생성자가 아니라는 것 같음.

metatype is not a constructor에러는 커스텀 가드를 사용할 때 발생할 수 있는 일반적 오류중 하나이며

@UseGuards() 데코레이터에 가드 클래스를 전달할 때 메타타입 정보가 올바르게 전달되지 않을 때 발생한다고 한다.

즉, 인스턴스를 전달하는 것이 아니라 생성자 함수를 전달해야한다.

//before
@UseGuards(AuthGuard)

//after
@UseGuards(AuthGuard())

문제가 된 부분을 수정해주었음.

 

 

새로운 에러

In order to use "defaultStrategy", please, ensure to import PassportModule in each place where AuthGuard() is being used, Otherwise, passport won't work correctly.

 

defaultStrategy를 사용하려면 AuthGuard가 사용되는 곳에 PassportModule을 import해주어야 한다고 한다.

 

나의 경우 AuthModule에서 사용중이니 임포트 해주었다.

@Module({
    providers: [AuthService],
    controllers: [AuthController],
    exports: [AuthService],
    imports: [
      UserModule,
      //추가한 부분
      PassportModule.register({ defaultStrategy: 'jwt' }),
            
      JwtModule.registerAsync({
      (...생략...)
    ],
  })
  export class AuthModule {}

 

 

 

 

참조

https://stackoverflow.com/questions/67066064/error-metatype-is-not-a-constructor-when-using-instance-of-own-https-server-cla

 

Error: metatype is not a constructor when using instance of own HTTPS Server class

Good evening, I am playing around with nest and want to achieve an own HTTPS-Server that can be instantiated everywhere in other projects. Right at the beginning I get the following error-message:

stackoverflow.com

https://velog.io/@wanzekim/ERROR-ExceptionHandler-metatype-is-not-a-constructor

 

[Nest.js] ERROR [ExceptionHandler] metatype is not a constructor

@UseGuards(AuthGuard()) 로 발생한 이슈

velog.io

 

300x250
mag1c

mag1c

2년차 주니어 개발자.

[NestJS] NestJS 개발 환경 셋팅

Tech/JavaScript & TypeScript 2023. 9. 9. 00:20
728x90

 

 

서론

9개월 정도를 스프링만 사용하던 신입 개발자인 나에게 갑작스레 실무에서 Node.js를 사용해야하는 상황이 닥쳤다.

기존 backend 코드를 보니 모듈을 import하는 것들이 대부분 @nestJS/어쩌고로 되어있는 typescript였다.

 

실무를 위해 빠른 적응이 필요했다.

나중에 node와 typescript, nest등에 대한 적당히 자세한(?) 공부 및 포스팅을 진행할 것이다.

 

간단하게 알아본 바로는 다음과 같았다.

NestJS는 서버 측 노드 애플리케이션을 구축하기 위한 프레임워크로

Express와 같은 HTTP Server Framework를 내장하고 있고, 대부분 TypeScript로 구성되어 있다고한다.

 

이미 셋팅되어있는 개발환경을 다시 뜯어보기전에, 어떻게 개발환경을 셋팅하는지 부터 알아야했다.

 

Nest는  Angular에서 영감을 받아 개발자와 팀이 고도로 테스트 가능하고 확장 가능하며 느슨하게 결합되고유지 관리가 쉬운 애플리케이션을 만들 수 있는, 즉시 사용 가능한 애플리케이션 아키텍처를 제공한다.

 

 

Documentation | NestJS - A progressive Node.js framework

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Rea

docs.nestjs.com

 

 

개발 환경 셋팅

노드 설치

노드를 우선 설치해야하는데, 노드 설치는 홈페이지 가서 OS에 맞는 것을 설치하면 된다.

 

NestJS CLI 설치

CLI란 명령 줄 인터페이스로 NestJS는 개발자가 좀 더 편리하게 개발하고 설정할 수 있도록 이러한 CLI를 제공한다.

npm i -g @nestjs/cli

혹여 권한 때문에 에러가 발생한다면 sudo 등의 권한을 부여하는 명령어를 같이 사용하자.

나는 그냥 VSCode를 관리자 권한으로 실행했더니 잘 됐다.

 

위 명령어를 통해 설치를 진행하며, 왜 꼭 전역으로 설치해야하는지 의문이 들어서 검색해보다가 결국 chatGPT에게 물어봤더니 아래처럼 대답해줬다.

 

 

프로젝트 생성

nest new projectName

원하는 경로로 이동하여 위 명령어를 실행하면 NestJS CLI가 자동으로 프로젝트 폴더를 생성해준다.

 

 

 

자동으로 프로젝트가 생성되었다.

300x250
mag1c

mag1c

2년차 주니어 개발자.

자주쓰는 npm 명령어 정리 (npx)

Tech/JavaScript & TypeScript 2023. 9. 7. 23:41
728x90

 

 

명령어 정리

npx <command>
npm 5.2.0버전부터 제공되는 명령어로
npx를 사용하면 install 없이 일회성으로 패키지를 실행하거나 명령을 실행할 수 있다.

1. npm init

 - package.json 생성

 

2. npm install / npm i

 - npm 모듈을 로컬(현재 프로젝트 내부)에 설치

 - 옵션으로 --save / -S, --save-dev / -D를 사용 가능

 - --save / -S는 dependency에 추가하는 명령어인데 npm 5버전 이후로는 자동으로 dependency에 추가됨

- --save-dev / -D는 devDependencies에 추가. 개발 환경에서만 사용하는 모듈

 

3. npm uninstall moduleName

 - 설치한 모듈 삭제

 

4. npm dedupe

 - 중복된 모듈 정리

 

5. npm root

 - node_modules의 위치를 알려줌

 

6. npm oudated

 - 오래된 패키지의 존재 유무를 알려줌

 

7. npm ls ( + 패키지명)

 - 패키지 조회 (패키지의 유무와 어떤 dependecies인지 알려줌)

 

8. npm cache (clean --force)

 - npm 내의 cache 확인 (캐시 삭제)

 

9. npm rebuild

 - npm 재설치

 

10. npm -v / npm - version

 - 버전 확인

 

11. npm start

 - package.json의 scripts에 있는 start명령어 실행

 

11. npm stop

 - 실행중인 npm 중지

 

12. npm restart

 - 재시작

 

13. npm run (명령어)

 - 그 이외의 scripts 실행

 

14. npm config (list)

 - npm 설정 조작 (현재 설정 조회)

300x250
mag1c

mag1c

2년차 주니어 개발자.

[백준 10845 / Java] 큐

Tech/JavaScript & TypeScript 2023. 7. 1. 09:07
728x90

문제 링크

https://www.acmicpc.net/problem/10845

 

10845번: 큐

첫째 줄에 주어지는 명령의 수 N (1 ≤ N ≤ 10,000)이 주어진다. 둘째 줄부터 N개의 줄에는 명령이 하나씩 주어진다. 주어지는 정수는 1보다 크거나 같고, 100,000보다 작거나 같다. 문제에 나와있지

www.acmicpc.net


 

 

 

풀이

큐 포스팅 링크

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

public class Main {

    static List<Integer> queue = new ArrayList<>();
    static StringBuffer sb = new StringBuffer();

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st;

        int N = Integer.parseInt(br.readLine());

        for(int i = 0; i < N; i++){
            st = new StringTokenizer(br.readLine(), " ");

            switch(st.nextToken()){
                case "push": push(Integer.parseInt(st.nextToken()));
                    break;
                case "pop" : pop();
                    break;
                case "size" : size();
                    break;
                case "empty" : empty();
                    break;
                case "front" : front();
                    break;
                case "back" : back();
                    break;
            }
        }
        System.out.println(sb.toString());
    }

    private static void push(int i) {
        queue.add(i);
    }

    private static void pop() {
        if(queue.isEmpty()) sb.append(-1 + "\n");
        else{
            sb.append(queue.remove(0) + "\n");
        }
    }

    private static void size() {
        sb.append(queue.size() + "\n");
    }

    private static void empty() {
        if(queue.isEmpty()) sb.append(1 + "\n");
        else sb.append(0 + "\n");
    }

    private static void front() {
        if(queue.isEmpty()) sb.append(-1 + "\n");
        else{
            sb.append(queue.get(0) + "\n");
        }
    }

    private static void back() {
        if(queue.isEmpty()) sb.append(-1 + "\n");
        else{
            sb.append(queue.get(queue.size() - 1) + "\n");
        }
    }

}
300x250
mag1c

mag1c

2년차 주니어 개발자.

[JavaScript] 식사 메뉴를 선정을 위한 돌림판(룰렛) 만들기

Tech/JavaScript & TypeScript 2023. 3. 15. 07:32
728x90

결정장애라 메뉴선정시에 항상 어려움을 겪곤 한다.

학원 수강 중 메뉴 선정에 대한 고민을 하였고, 결국 오픈소스를 활용하여 돌림판 제작을 시작했다.

 

 

See the Pen Untitled by magic (@mag11c) on CodePen.

 

 

자주가는 식당들을 product 배열에 미리 추가한 뒤,

해당 배열의 길이와 동일하게 색상 배열을 만들어 색상을 미리 추가시켰다.

그리고 새로운 메뉴를 추가할 수 있다.

const $c = document.querySelector("canvas");
const ctx = $c.getContext(`2d`);
const menuAdd = document.querySelector('#menuAdd');
const product = ["햄버거", "순대국", "정식당", "중국집", "구내식당"];
const colors = [];

const newMake = () => {
	const [cw, ch] = [$c.width / 2, $c.height / 2];
	const arc = Math.PI / (product.length / 2);  
	for (let i = 0; i < product.length; i++) {
		ctx.beginPath();
		if(colors.length == 0){
			for(var l=0; l<product.length; l++){
				let r = Math.floor(Math.random() * 256);
				let g = Math.floor(Math.random() * 256);
				let b = Math.floor(Math.random() * 256);
				colors.push("rgb(" + r + "," + g + "," + b + ")");
			}
		}
		ctx.fillStyle = colors[i % (colors.length)];
		ctx.moveTo(cw, ch);
		ctx.arc(cw, ch, cw, arc * (i - 1), arc * i);
		ctx.fill();
		ctx.closePath();
	}

	ctx.fillStyle = "#fff";
	ctx.font = "18px Pretendard";
	ctx.textAlign = "center";

	for (let i = 0; i < product.length; i++) {
		const angle = (arc * i) + (arc / 2);

		ctx.save();

		ctx.translate(
			cw + Math.cos(angle) * (cw - 50),
			ch + Math.sin(angle) * (ch - 50)
		);

		ctx.rotate(angle + Math.PI / 2);

		product[i].split(" ").forEach((text, j) => {
			ctx.fillText(text, 0, 30 * j);
		});

		ctx.restore();
	}
}

const rotate = () => {
	$c.style.transform = `initial`;
	$c.style.transition = `initial`;
	const alpha = Math.floor(Math.random()*100);

	setTimeout(() => {    
		const ran = Math.floor(Math.random() * product.length);
		const arc = 360 / product.length;
		const rotate = (ran * arc) + 3600 + (arc * 3) - (arc/4) + alpha;
		$c.style.transform = `rotate(-${rotate}deg)`;
		$c.style.transition = `2s`;
    
	}, 1);
};


function add(){
	if(menuAdd.value != undefined && menuAdd.value != ""){
		product.push(menuAdd.value);
		let r = Math.floor(Math.random() * 256);
		let g = Math.floor(Math.random() * 256);
		let b = Math.floor(Math.random() * 256);
		colors.push("rgb(" + r + "," + g + "," + b + ")");
		newMake();
		menuAdd.value="";
	}
	else{
		alert("메뉴를 입력한 후 버튼을 클릭 해 주세요");
	}
}

newMake();

 

 

 

참조


아래 블로그의 오픈소스를 활용하여 제 상황에 맞게 약간 변경하였습니다.

https://gurtn.tistory.com/180

 

[JS] 룰렛 구현하기

JavaScript와 캔버스(Canvas)를 사용하여 룰렛을 구현해보았습니다. 코드 See the Pen Canvas Roulette by hyukson (@hyukson) on CodePen. 코드 풀이 const $c = document.querySelector("canvas"); const ctx = $c.getContext(`2d`); // 룰렛

gurtn.tistory.com

 

300x250
mag1c

mag1c

2년차 주니어 개발자.

[JavaScript] localStorage를 활용한 todoList 만들기

Tech/JavaScript & TypeScript 2023. 1. 22. 21:05
728x90

HTML

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="style.css" />
    <title>해야할이일</title>
  </head>
  <body>
    <h1>todos</h1>
    <form id="form">
      <input type="text" class="input" id="input" placeholder="Enter your todo" autocomplete="off">

      <ul class="todos" id="todos">

      </ul>
    </form>
    <small>Left click to toggle completed. <br> Right click to delete todo</small>

    <script src="script.js"></script>
  </body>
</html>

 

input에서 입력된 값을 ul의 li에다가 담을 것이며, 스토리지를 이용해 새로고침해도 리스트가 남아있게 할 예정이다.

자바스크립트 공부를 위함이니 css는 맨 아래에다가 작성 해 두겠다.

 

JS

새로고침 이후에도 유지되기 위해 스토리지를 활용할 것이다.

localStorage에 데이터를 넘겨주기 위해 배열을 JSON.stringify() 메서드로 포맷팅한 뒤 넘겨주었다.

const form = document.querySelector('form');
const input = document.querySelector('input');
const ul = document.querySelector('ul');

const todos = "TODOS";
let arr = new Array();

// localStorage.clear();

//저장 시 obj -> string
function save(){
    localStorage.setItem(todos, JSON.stringify(arr))
}

function put(text){
    //필요한 li 생성작업
    const li = document.createElement('li');
    const liId = arr.length + 1;
    li.textContent = text;
    ul.appendChild(li);
    li.id = liId;
    
    //json형태로 정보저장
    const Obj = {
        id : liId,
        text,
    };

    arr.push(Obj);
    save();
}


function submit(e){
    //form기능 중단
    e.preventDefault();
    const curVal = input.value;
    put(curVal);
    //form기능을 멈췄기때문에 put뒤에 반드시 벨류초기화
    input.value='';
}

function load(){
    const loading = localStorage.getItem(todos);

    //스토리지에 값이 있나 확인하는 작업.
    if(loading!==null){
        const json = JSON.parse(loading); //stringify -> json(Obj)
        //jsonobj중 text만 받아서 put(li에 추가)   
        json.forEach(function (j){
            put(j.text);
        })
    }
}

//init에서 load를 불러오고, submit기능중단을 위한 form의 이벤트 추가
function init(){
    load();
    form.addEventListener('submit', submit);
}

init();

 

CSS

@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@200;400&display=swap');

* {
  box-sizing: border-box;
}

body {
  background-color: #f5f5f5;
  color: #444;
  font-family: 'Poppins', sans-serif;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100vh;
  margin: 0;
}

h1 {
  color: rgb(179, 131, 226);
  font-size: 10rem;
  text-align: center;
  opacity: 0.4;
}

form {
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
  max-width: 100%;
  width: 400px;
}

.input {
  border: none;
  color: #444;
  font-size: 2rem;
  padding: 1rem 2rem;
  display: block;
  width: 100%;
}

.input::placeholder {
  color: #d5d5d5;
}

.input:focus {
  outline-color: rgb(179, 131, 226);
}

.todos {
  background-color: #fff;
  padding: 0;
  margin: 0;
  list-style-type: none;
}

.todos li {
  border-top: 1px solid #e5e5e5;
  cursor: pointer;
  font-size: 1.5rem;
  padding: 1rem 2rem;
}

.todos li.completed {
  color: #b6b6b6;
  text-decoration: line-through;
}

small {
  color: #b5b5b5;
  margin-top: 3rem;
  text-align: center;
}
300x250
mag1c

mag1c

2년차 주니어 개발자.

[JavaScript] 웹 스토리지(Web Storage)

Tech/JavaScript & TypeScript 2023. 1. 22. 20:14
728x90

웹 스토리지 ( Web Storage )


기존 쿠키의 문제점을 보완하기 위해 사용한다.

https://mag1c.tistory.com/187
 

쿠키와 세션 (Cookie & Session)

HTTP 프로토콜의 특징 비연결성 ( Connectionless ) 클라이언트가 서버에 요청(Request)할 때, 그에 대한 응답(Response)을 한 후, 연결을 끊는다. 비상태성 ( Stateless ) 클라이언트의 상태 정보를 가지지 않는

mag1c.tistory.com

 

 

웹 스토리지의 특징

1. 브라우저 내부에 Key, Value 쌍을 저장하는 공간이다.

2. 네트워크 요청 시 서버로 전송되지 쿠키에 비해 않아 많은 자료보관이 가능하다. (2MB이상)

3. 서버가 HTTP 헤더를 통해 스토리지 객체를 조작할 수 없다. 자바스크립트 내에서 조작이 가능하다.

4. 웹 스토리지 객체는 오리진(origin)에 묶여있다. 따라서 프로토콜과 서브 도메인이 다르면 데이터에 접근할 수 없다.

오리진(origin)영역

도메인, 프로토콜, 포트로 정의되는 영역

 

//사용자의 브라우저가 웹 스토리지의 지원 여부를 확인한다.
if(typeof(Storage)!=="undefined") alert("사용가능")
else alert("사용 불가")

 

 

스토리지 또한 개발자모드(F12)의 Application 탭에서 확인 가능하다.

 

 

메서드

메서드 설명
setItem(key, value) key, value 저장
getItem(key) key에 해당하는 value 받기
removeItem(key) 해당 key와 그에 맞는 value 삭제
clear() 스토리지 초기화
key(index) 인덱스에 해당하는 key값 받기
length 저장된 개수

 

 

localStorage vs sessionStorage

브라우저를 껐다가 켜도 데이터가 남아있다 - localStorage

페이지를 새로고침해도 데이터가 남아있지만, 브라우저를 종료하면 사라진다 - sessionStorage

 

 

 

 

사용예제


<div id="cnt"></div>
<p><button id="btn">버튼</button></p>

<script>
    const btn = document.querySelector('#btn');
    btn.addEventListener('click', function(){
        if(typeof(Storage) !=="undefined"){
            if(localStorage.cnt){
                localStorage.cnt = Number(localStorage.cnt) +1;
            }else{
                localStorage.cnt = 1;
            }
            document.querySelector("#cnt").innerHTML="세션카운트 : " + localStorage.cnt;
        }
    })
</script>

 

 

 

 

300x250
mag1c

mag1c

2년차 주니어 개발자.

방명록