Leon Chaewon Kong's dev blog

Deno로 서버 개발 시작해보기

Node.js를 만든 개발자 라이언 달(Ryan Dahl)은 2018년 Node.js의 단점을 개선하기 위해 JavaScript/TypeScript를 위한 새 런타임인 Deno를 선보였습니다.

Node.js에 기반한 Express.js 한동안 매우 핫한 서버 프레임워크였고, 비록 서버에서의 Node.js의 인기는 사그라들었지만 여전히 프론트엔드에서 떼려야 뗄 수 없는 존재입니다.

NPM으로 설치되는 모든 프론트엔드 모듈들은 Node.js를 기반으로 설치/관리되죠. node_modules라는 디렉토리 역시 Node.js의 발상입니다.

한편, Node.js는 몇가지 단점도 존재합니다.

  • 중앙 집중형 모듈 시스템
  • 빈약한 보안 (리소스에 대한 권한 설정이 불가해 누구나 접근 가능)

출처) Node 제작자가 만든 Deno: 자바스크립트의 새로운 접근

이런 문제들을 해결하기 위해 나온 런타임이 바로 Deno 입니다.

ES 모듈 시스템이 도입되었고, 보안이 강화되었으며, 브라우저 호환성이 개선되었다고 합니다.

오늘은 이런 Deno를 한번 사용해보고 가볍게 정리해 보려 합니다.

2020년, 드디어 1.0 버전(정식 버전)이 출시되었고, 현재는 1.6.1 버전까지 왔는데요. 아직 Node.js를 대체하는 정도까지는 아니지만, 이제는 상용 프로젝트에서 사용해볼만 하지 않을까 하는 궁금증을 풀어보기 위해서 입니다.

설치

맥(Mac) - Brew 이용

brew install deno

맥 / 리눅스

curl -fsSL https://deno.land/x/install/install.sh | sh

윈도우 - 파워쉘

iwr https://deno.land/x/install/install.ps1 -useb | iex

설치한 후,

deno --help

명령어를 입력해 정상적으로 설치되었는지 확인합니다. 혹은,

deno run https://deno.land/std/examples/welcome.ts

이렇게 deno 측에서 제공하는 welcome.ts 파일을 실행하고 바로 결과를 터미널에 출력해 볼 수도 있습니다.

보셨겠지만, TypeScript가 별도의 컴파일 없이 바로 이용 가능합니다.

간단한 서버 만들기

  1. mkdir deno-project로 폴더를 생성하고, 해당 폴더로 이동합니다.
  2. touch server.ts로 서버 스크립트를 담을 파일을 생성합니다.

이제 간단한 서버를 구현해 보겠습니다. deno 도큐멘테이션을 참고했습니다.

import { serve } from "https://deno.land/std@0.83.0/http/server.ts";

const server = serve({ hostname: "0.0.0.0", port: 8080 });
console.log(`HTTP webserver running.  Access it at:  http://localhost:8080/ 🦕`);

(async function handleHttpRequest () {
  for await (const request of server) {
    let bodyContent = "Your user-agent is:\n\n";
    bodyContent += request.headers.get("user-agent") || "Unknown";

    request.respond({
      status: 200,
      body: `
      Hello Deno!
                    __
                    / _)
          _.----._/ /
          /         /
      __/ (  | (  |
      /__.-'|_|--|_|
      `
    });
  }
})()

실행은 deno run으로 하면 되는데, 그냥 하게되면 아래같은 오류가 발생합니다.

deno run server.ts
# th the --allow-net flag
#     at processResponse (deno:core/core.js:223:11)
#     at Object.jsonOpSync (deno:core/core.js:246:12)
#     at opListen (deno:cli/rt/30_net.js:32:17)
#     at Object.listen (deno:cli/rt/30_net.js:207:17)
#     at serve (server.ts:306:25)
#     at server.ts:3:16

Deno가 보안이 강화되었다고 말씀드렸었는데요. permission과 관련한 flag를 주지 않아 발생한 에러입니다. --allow-net 옵션을 줘야 합니다.

--allow-net=<allow-net> 옵션은 네트워크 액세스를 허용하는 옵션입니다. 쉼표로 구분된 도메인 리스트를 옵셔널하게 줄 수 있는데, 그 경우 해당 도메인에서만 네트워크 접근이 가능해 집니다.

deno run --allow-net server.ts
# HTTP webserver running.  Access it at:  http://localhost:8080/

http://localhost:8080으로 이동해 공룡이 잘 프린팅 되고 있는지 확인합니다.

정리해보기

아주 간단한 서버를 작성해 보았는데요. 이것만으로도 Node.js와는 완전히 다르다는 것을 알 수 있습니다.

먼저, import문 부터 충격적이죠.

import

Deno는 Local ImportRemote Import를 각각 지원합니다.

// Local Import
import { add } from './arithmetic.ts';

// Remote Import
import { add } from 'ttps://x.nest.land/ramda@0.27.0/source/index.js';

Local Import는 우리가 익숙한 방식의 import이고, Remote Import는 리모트에서 URL로 JavaScript 모듈을 바로(directly) 가져오는 방식입니다. 신기하죠.

export는 기존과 동일합니다.

for await …of

참고) MDN - for await…of 재밌는 점은, 아래처럼 사용해도 된다는 것입니다.

import { serve } from "https://deno.land/std@0.83.0/http/server.ts";

const server = serve({ hostname: "0.0.0.0", port: 8080 });
console.log(`HTTP webserver running.  Access it at:  http://localhost:8080/ 🦕`);

for await (const request of server) {
  let bodyContent = "Your user-agent is:\n\n";
  bodyContent += request.headers.get("user-agent") || "Unknown";

  request.respond({
    status: 200,
    body: `
    Hello Deno!
                  __
                  / _)
        _.----._/ /
        /         /
    __/ (  | (  |
    /__.-'|_|--|_|
    `
  });
}

보통 for await ...of문은 비동기실행이 전제되므로 async 함수 내에서만 이용이 가능합니다. 아래처럼 말이죠.

async function lambda () {
  for await (const req of server) {
    // ...
  }
}

하지만 Deno에서는 async function으로 래핑하지 않고도 사용이 가능합니다.

Permissions

보통 Node.js였다면 node server.js로 바로 실행되었을 것입니다. 하지만 Deno는 그렇지 않죠. 보안을 중시하기 때문입니다.

Deno는 다양한 Permission 옵션들을 제공합니다. 조금 전 살펴본 --allow-net 옵션처럼 말이죠.

Permission 리스트

  • A, –allow-all: 모든 permission을 허용합니다.
  • –allow-env: environment access를 허용합니다. 환경변수를 가져오거나 설정할 수 있습니다.
  • –allow-hrtime: high-resolution 시간 측정을 제공합니다.
  • –allow-net=: 네트워크 액세스를 허용합니다. 허용할 도메인을 쉼표로 구분해 제공해 명시적으로 특정 도메인만 허용할 수도 있습니다.
  • –allow-plugin: 플러그인을 불러올 수 있습니다. (stable 상태의 feature는 아니라고 합니다.)
  • –allow-read=: 파일시스템의 파일을 읽을 수 있는 권한을 부여합니다. 쉼표로 구분해 디렉토리나 파일을 특정할 수 있습니다.
  • –allow-run: 서브프로세스를 실행할 수 있는 권한을 부여합니다. 서브프로세스는 샌드박스 내에서 실행되는 것이 아니므로, 본 프로세스와 동일한 보안 혜택을 받지 못합니다. 위험합니다.
  • –allow-write=: 파일시스템의 파일을 작성/수정을 수 있는 권한을 부여합니다. 쉼표로 구분해 디렉토리나 파일을 특정할 수 있습니다.

TypeScript를 직접 지원

그렇습니다. 타입스크립트 모듈도 common-JS로 컴파일하지 않아도 Deno에서는 바로 import해 사용할 수 있습니다.

import { serve } from "https://deno.land/std@0.83.0/http/server.ts";

마찬가지로, 컴파일하지 않아도 Deno는 타입스크립트 파일을 런타임에서 실행할 수 있습니다.

Deno run something.ts

마치며

가볍게 Deno를 알아보고, 사용해 보았습니다.

무엇보다 TypeScript를 default로 지원한다는 점이 매우 맘에 듭니다.

NPM을 경유한 모듈 설치 없이, 필요한 파일을 원격(Remote)에서 URL을 통해 import 하는 방식으로 사용할 수 있다는 점도 매력적입니다.

강력한 permission 기능에 기반한 보안도 그렇고요.

더 자세한 내용은 매뉴얼을 읽읍시다!!

감사합니다.