티스토리 뷰

올해 초에 쓰기 시작했는데..

연말이 돼서야 겨우 완료했습니다. 

실무 개발을 해 보면서 프로젝트를 진행할 때, 최소한 이런 건 알고 하면 좋겠다 싶은 것들을 넣다 보니까 페이지가 많이 늘어나 버렸습니다.

뭔가 하나를 깊게 파는 서적인 아니지만, 전체적인 흐름 파악이나 개발의 선택 과정을 파악 할 때는 도움이 될 것이라는 생각으로 글을 썼습니다.

 

책 소개 

 

타입스크립트를 활용한 웹 애플리케이션 개발 가이드 북

 

현재 웹 애플리케이션을 개발하기 위한 많은 개발 환경과 그 환경에 맞는 다양한 방법들이 생겨나고 있다. 그래서 우리는 어떤 방법으로 개발을 시작해야 할지 선택하기를 주저하게 된다.

 

이 책에서는 실무에 적합한 플랫폼과 다양한 개발 프레임워크 및 라이브러리를 소개하고, 각 상황에 맞는 플랫폼 및 프레임워크를 선택하고 개발하는 과정을 안내한다. 이런 과정을 통해서 개발의 식견을 넓히고, 새로운 개발 방법을 학습하여 새로운 프로젝트가 생길 때 다양한 구현 방법을 검토하고 결정할 수 있도록 도와준다. 독자 여러분도 이 책만 있으면 어떤 프로젝트든지 훌륭하게 완료할 수 있을 것이다.

 

-    서버리스 및 AWS 서비스 소개

-    Typescript 기본 문법

-    VSCODE를 이용한 개발 환경 구성

-    Typescript를 사용한 AWS 람다 서비스 구성

-    Typescript를 이용한 Vue.js 개발

-    Vue.js에서 UI Framework 사용하기

-    Docker 기본 사용법

-    Typescript를 이용한 ORM 사용

-    Typescript를 이용한 GraphQL 사용

-    TDD 기본 사용법

-    AWS를 통한 배포

 

이 책의 특징 

- 가장 최신의 모던한 웹 애플리케이션 개발 과정을 제공한다.

- 플랫폼 선택부터 웹 애플리케이션 개발 과정까지 다양한 선택 과정을 배울 수 있다.

- 실무 웹 개발에 필요한 다양한 방법 및 개발 환경 구성을 배울 수 있다.

 

이 책이 필요한 독자 

- 웹 개발을 시작하시는 분

- 웹 개발 경험이 있으면서 새로운 개발 환경이 궁금하신 분

- 최신 웹 개발 트렌드에 관심이 있으신 분

 

독자대상 

초중급 

 

소스코드 다운로드 

https://github.com/bjpublic/typescript



출처: https://bjpublic.tistory.com/346 [비제이퍼블릭]

 

온라인 판매

 

타입스크립트, AWS 서버리스로 들어올리다

타입스크립트를 활용한 웹 애플리케이션 개발 가이드 북 현재 웹 애플리케이션을 개발하기 위한 많은 개발 환경과 그 환경에 맞는 다양한 방법들이 생겨나고 있다. 그래서 우리는 어떤 방법으로 개발을 시작해야 할지 선택하기를 주저하게 된다.본서에서는 실무에 적합한 플랫폼과 다양한 개발 프레임워크 및 라이브러리를 소개하고, 각 상황에 맞는 ...

www.yes24.com

 

타입스크립트, AWS 서버리스로 들어올리다

실무에 적합한 플랫폼과 다양한 개발 프레임워크 및 라이브러리를 소개하고, 각 상황에 맞는 플랫폼 및 프레임워크를 선택하고 개발하는 과정을 안내한다. 새로운 개발 방법을 학습하여 새로운 프로젝트가 생길 때 ...

www.aladin.co.kr

 

타입스크립트, AWS 서버리스로 들어올리다

현재 웹 애플리케이션을 개발하기 위한 많은 개발 환경과...

www.kyobobook.co.kr

 

댓글
  • 프로필사진 adanB 서버리스 설정을 책에 나온데로 하고 포스트맨에서 get 을 호출했을때 계속 타임아웃이 뜹니다. 서버리스에서 호출된 함수에서 함수가 실행되는 점은 확인하였습니다. 무엇이 문제일까요?ㅠ 2020.01.03 23:30 신고
  • 프로필사진 신규하 타임아웃이 뜬다면 잘 못 된 주소를 호출 했을 수 있습니다.
    어떤 챕터의 어떤 예제인지 알려주시면 좀 더 자세한 답변을 해 드릴 수 있을 것 같습니다.
    2020.01.06 15:39 신고
  • 프로필사진 곰딩 책 잘 보고 있습니다~ 간단한 질문이 있습니다. ";" 세미콜론을 입력하는 기준이 있나요?
    139쪽 7.6 함수 코드에서는 라인 당 세미콜론이 찍혀 있는데,
    141쪽 7.7 인터페이스의 샘플코드 size: Size 에는 세미콜론이 없고,
    또, 142쪽 샘플코드 내에서 인터페이스 부분은 세미콜론이 있고, 펑션부분에는 세미콜론이 없어서...
    그 기준이 궁금합니다!

    한가지 더 질문을 드리면,,,
    165쪽 8.5 다이나모디비 로컬 설치 파트에서,,
    설명대로 잘 따라한거 같은데,
    sls offline start 를 실행하면
    다이나모디비 접속 주소가 나와야 하는 라인에,
    Serverless: Skipping start: DynamoDB Local is not available for stage: dev
    위와 같은 메시지가 출력되어, 디비에 접속할 수가 없습니다. 해결방법 아시면 댓글 부탁드려요.
    구글링을 해 봐도 이틀째 해결을 못하고 있네요 ㅠㅠ
    2020.01.07 09:54
  • 프로필사진 신규하 먼저 잘 봐주신다니 감사합니다.
    기본적으로 자바스크립트에서는 세미콜론을 쓰지 않아도 무방합니다.
    그런데 타입스크립트에서는 tslint.js 파일에서 사용 여부를 결정 할 수 있습니다.
    본 예제에서는 웹사이트에서 컴파일 되는걸 테스트 하면서 작성한 코드라서 세미콜론이 누락 되었네요.
    하지만, 실행에는 별 지장이 없습니다.

    로컬 다이나모디비의 경우에는 자바로 실행이 되게 됩니다.
    그래서, 로컬에 자바를 꼭 설치 하셔야 합니다.
    https://java.com/ko/download

    위 사이트에서 자바 다운로드 받아서 설치 후 터미널 또는 명령어 프롬프트에서 "java -version"으로 자바가 설치가 되어 있는지를 확인 해 주세요.
    설치가 확인 되었으면..

    sls dynamodb install

    로 다이나모디비를 재설치 해 주세요.


    2020.01.09 10:41 신고
  • 프로필사진 anjwoc 이번에 책을 사고 하루만에 반절은 본거같은데요!! 너무 감사합니다 ㅠ 책값이 졸업 직전인 취준생입장에서 싸지는 않았지만 딱 제가 당장 공부해보고싶던게 다 있네요 ㅎㅎ aws 람다도 궁금했었고 기존에 vue에 node만 사용해봐서 타입스크립트도 도입해보고싶었고 회사들이 그렇게 요구하는 TDD도 공부해보고 싶었는데 책하나에 모두 있어서 너무 알차게 공부하는중입니다!
    우연히 블로그를 봐서 글남기고 갑니다 ㅎㅎ
    2020.01.20 18:49
  • 프로필사진 신규하 잘 봐주신다니.. 너무너무 감사합니다.
    보시다 궁금하신게 있다면 글 남겨 주세요.
    2020.01.21 19:46 신고
  • 프로필사진 _하늘여우_ 질문이 있습니다. ^^;
    p.73의 람다함수 생성하는 곳에서 '그림 4-6'을 보면 helloWorldFunction 하위에 Amazon CloudWatch Logs 가 표기되어 있는데, 저는 cloudwatch logs가 생성되지 않는데요. 이 항목을 추가시키려면 어떻게 설정하면 될까요?? 구글링해보았으나.. 제가 못 찾은건지.. 방법을 모르겠어서 질문 드립니다. m(_ _)m
    2020.01.30 05:58 신고
  • 프로필사진 _하늘여우_ 더불어서...
    p.100 에서 aws-cli 로 람다함수 만드는 설명에서요. 윈도우에서 실습 중인데,
    "aws lambda create-function ~ " 부분은 콘솔에 바로 입력하나요?? 콘솔에 한 번에 입력하기 힘들어 메모장에 작성 후 붙혀넣기하면 자꾸 잘리는데... 해결할 수 있는 방법이 있을까요?? ㅠㅠ
    2020.01.30 07:32 신고
  • 프로필사진 신규하 AWS의 블루프린터로 만드시면 기본적으로 포함이 되어 있습니다.
    아마도, 초기부터 만드셔서 포함이 되지 않은거 같습니다.
    2020.01.30 22:11 신고
  • 프로필사진 신규하 더불어서...
    윈도우에서 긴 명령어는 붙여 넣기를 해도 괜찮지만, bat 파일을 만드셔서 실행해 주시면 더욱 편하게 사용이 가능 할 것 같습니다.
    예를 들면, aws-lambda-create.bat 파일을 만들고..

    aws lambda create-function --function-name helloworld --zip-file

    이렇게 넣어 주고.. cmd에서 파일명을 넣고 실행 해 주시면 됩니다.
    2020.01.30 22:14 신고
  • 프로필사진 임원진 안녕하세요~
    책 너무 잘 보고 있습니다^^
    한 가지 궁금한게 있어서요~
    p.185에서 const data를 정의하는 부분에 자료형이 ITodo로 되어 있는데~
    이 자료형이 다른 곳에 정의 되어 있는 건가요? (제가 놓친 것인지 ;;)
    can not find name ITodo 라고 나와서요~
    답변 부탁드립니다. ^^;;;;
    2020.02.12 18:44
  • 프로필사진 신규하 안녕하세요. 잘 봐 주신다니 정말 감사드립니다.
    1쇄에서 우선 누락된 내용이 있었습니다 ㅜ.ㅜ
    누락된 내용은 아래와 같습니다.

    --------------
    src/todo/index.d.ts 파일 만들고 아래와 같이 입력한다.

    interface ITodo {
    id: string;
    task: string;
    isCompleted: boolean;
    deletedAt: number;
    createdAt: string;
    }

    index.d.ts파일은 src/todo 폴더에 있는 소스 파일에서 ITodo 인터페이스를 별도의 설정이나 import가 없이 사용 할 수 있게 해 준다.

    -----
    추가로 누락된 내용이나 변경 사항은 아래를 참고 해 주시면 도움이 되실 겁니다.

    https://github.com/bjpublic/typescript/issues

    감사합니다.
    2020.02.13 23:49 신고
  • 프로필사진 안녕하세요.
    예제 따라하는 중에 막히는 부분이 있어서 질문드립니다.
    p101의 만든 함수 실행하는 cli 예제 똑바로 입력했는데
    무효한 base 64라고 나오고 안됩니다. ㅜㅜ

    mac os 사용 중입니다.
    ----------
    aws lambda invoke --function-name helloworld --log-type Tail \
    --payload '{"key1":"value1", "key2":"value2", "key3":"value3"}' \
    outputfile.txt

    Invalid base64: "{"key1":"value1", "key2":"value2", "key3":"value3"}"
    ----------
    2020.02.22 18:53
  • 프로필사진 신규하 안녕하세요..
    제가 Mac Os가 없어서 정확한 확인은 어렵지만, MacOS에서 비슷한 경우의 질문이 올라 온 것을 발견 했습니다.

    https://forums.aws.amazon.com/thread.jspa?messageID=933800&tstart=0

    마땅한 답변이 올라오면.. 다시 올려드리겠습니다.
    2020.02.23 02:44 신고
  • 프로필사진 wjlim 안녕하세요^^
    어느덧 책의 마지막 부분을 향해 가고 있습니다.
    질문이 있어서요~

    chapter18에서 18.1은 아무 문제 없이 진행 되었습니다.
    하지만 18.2를 진행하여 해당 주소로 들어가 보면~
    {
    "error": "Response not successful: Received status code 404"
    }
    라고 나오네요~
    알려주신 github에서 코드를 다운 받아 실행해도 마찬가지였습니다.
    어떤 문제가 있을까요?
    몇 일째 매달리고 있는데 답답하네요 ㅠ(운영체제 우분투 사용중에 있습니다.)

    > board@1.0.0 start /home/kirin/extypescript/board
    > serverless offline --stage dev --host 0.0.0.0

    Serverless: Compiling with Typescript...
    Serverless: Using local tsconfig.json
    Serverless: Typescript compiled.
    Serverless: Watching typescript files...
    offline: Starting Offline: dev/us-east-1.
    offline: Offline [http for lambda] listening on http://0.0.0.0:3002

    ┌───────────────────────────────────────────┐
    │ │
    │ ANY | http://0.0.0.0:3000/dev/graphql │
    │ │
    └───────────────────────────────────────────┘

    offline:
    offline: [HTTP] server ready: http://0.0.0.0:3000 🚀
    offline:
    offline: Enter "rp" to replay the last request
    offline:

    offline: ANY /dev/graphql (λ: graphql)
    offline: (λ: graphql) RequestId: ck8ffgnlu00028z7e72b9b6lx Duration: 774.98 ms Billed Duration: 800 ms
    2020.03.31 14:14
  • 프로필사진 신규하 먼저 18장까지 봐 주셔서 정말 감사드립니다...

    접속을 http://localhost:3000/dev/graphql이 아니라..

    http://localhost:3000/graphql

    이 주소로 접속해 보세요..

    콘솔에 ANY | http://0.0.0.0:3000/dev/graphql
    나오는건 아마도 serverless.yml을 편집하셔서 그렇게 나오는거 같습니다.

    그리고, 혹시나 해서 예제를 실행해 봤는데..
    정상적으로 잘 나오는거 같습니다.

    제가 ubuntu 18.04에서 실행한 내용은 아래와 같습니다.

    $ yarn start
    yarn run v1.21.1
    $ rm -rf .build
    $ serverless offline --stage dev --host 0.0.0.0
    Serverless: Compiling with Typescript...
    Serverless: Using local tsconfig.json
    Serverless: Typescript compiled.
    Serverless: Watching typescript files...
    Serverless: Starting Offline: dev/us-east-1.

    Serverless: Routes for graphql:
    Serverless: ANY /graphql
    Serverless: POST /{apiVersion}/functions/board-dev-graphql/invocations

    Serverless: Offline [HTTP] listening on http://0.0.0.0:3000
    Serverless: Enter "rp" to replay the last request

    혹시, 또 잘 안되면 댓글을 남겨주세요~
    감사합니다.
    2020.03.31 22:22
  • 프로필사진 wjlim 안녕하세요^^
    지난번에 답변을 잘 해 주셔서 현재 18.10 로그인 제작을 진행하고 있습니다.
    정말 감사합니다.
    그런데 18.10을 진행하던 도중 또 막히는 부분이 있어서 질문 드립니다^^;;
    코드를 전부 입력하고 start로 실행시켰는데 아래와 같이 yaml exception이 발생하였습니다.

    incomplete explicit mapping pair; a key node is missed; or followed by a non-tabulated empty line in "/home/kirin/extypescript/board/serverless.yml" at line 18, column 21:
    NODE_DEV: ${self: provider.stage}


    어떤 부분이 잘 못 된걸까요?ㅠ

    그리고 지금 예제를 진행하기 위해서는 docker-compose 등으로 mysql 데이터베이스가 설치되어 있어야 하나요?

    답변 부탁드립니다^^
    2020.04.16 21:55
  • 프로필사진 신규하 먼저 mysql은 꼭 docker-compose를 통해서 설치하실 필요는 없습니다.
    mysql을 직접 설치 하셔서 사용을 해도 괜찮습니다.

    serveless.yml에 오타가 있거나, 잘 못 구성된 것으로 보입니다.
    혹시, serveless.yml은 탭이나 스페이스를 잘 맞춰줘야 합니다. 들여쓰기가 잘 되어 있는지 확인해 보세요.

    그리고 발생하는 오류의 serverless.yml 파일을 공유해 주시면, 파악하는 데 도움이 될 것 같습니다.
    2020.04.17 17:39
  • 프로필사진 wjlim 답변 감사합니다^^
    serverless.yml은 git에서 다운 받은 18.10에 있는 serverless.yml을 그대로 복사 붙여넣기 하여 사용한 것입니다.

    service:
    name: board
    #app: your-app-name
    #tenant: your-tenant-name

    # Add the serverless-webpack plugin
    plugins:
    - serverless-plugin-typescript
    - serverless-offline

    provider:
    name: aws
    runtime: nodejs10.x
    region: ap-northeast-2
    stage: ${opt:stage, 'dev'}
    timeout: 30
    environment:
    NODE_DEV: ${self: provider.stage}
    MYSQL_HOST: ${self: custom.mysqlHost.${self:provider.stage}}
    MYSQL_PORT: ${self: custom.mysqlPort.${self:provider.stage}}
    MYSQL_USER: ${self: custom.mysqlUser.${self:provider.stage}}
    MYSQL_PASS: ${self: custom.mysqlPass.${self:provider.stage}}
    MYSQL_DATABASE: ${self: custom.mysqlDatabase.${self:provider.stage}}
    MYSQL_SYNC: ${self: custom.mysqlSync.${self:provider.stage}}
    MYSQL_LOG: ${self: custom.mysqlLog.${self:provider.stage}}
    SLS_DEBUG: true

    custom:
    serverless-offline:
    port: 3000
    stages:
    - dev
    - product
    mysqlHost:
    dev: "localhost"
    product: "xxxxx.rds.amazonaws.com"
    mysqlPort:
    dev: 3308
    product: 3306
    mysqlUser:
    dev: "board"
    product: "userid"
    mysqlPass:
    dev: "password"
    product: "awspassword"
    mysqlDatabase:
    dev: "board"
    product: "board"
    mysqlSync:
    dev: true
    product: true
    mysqlLog:
    dev: "all"
    product: "all"


    functions:
    graphql:
    handler: src/handler.graphql
    events:
    - http:
    path: graphql
    method: get
    cors: true
    - http:
    path: graphql
    method: post
    cors: true

    구글링을 해도 뭐가 문제인지 모르겠네요;;

    serverless.yml 에서는 들여쓰기가 되어 있는데 댓글로 옮기니 들여쓰기가 안되네요~

    댓글에는 파일 올리는 기능이 없어서 이렇게라도 남깁니다.
    2020.04.20 19:27
  • 프로필사진 신규하 stage: ${opt:stage, 'dev'}
    이 줄을
    stage: dev
    이렇게 바꾸고 실행해 봐 주시겠습니까?

    npm start 할 때
    serverless offline --stage dev --host 0.0.0.0
    이렇게 --stage옵션으로 빌드 타입을 선택하는데..
    여기서 뭔가 잘 안 가는 것 같습니다.

    제가 예제를 다시 실행해 봤는데..
    실행에는 이상이 없어 보였습니다.
    2020.04.21 22:36
  • 프로필사진 사주픽 [p207 페이지 AWS 람다 배포 문제]
    offline 에서는 API 가 정상적으로 실행되는데요.
    AWS 람다 배포 API에서는 아래와 같은 uuid import 문제가 발생하네요.
    구글링에도 답을 찾기어려워서 질문 남깁니다.

    Response:
    {
    "errorType": "Runtime.ImportModuleError",
    "errorMessage": "Error: Cannot find module 'uuid'\nRequire stack:\n- /var/task/src/todo/add.js\n- /var/runtime/UserFunction.js\n- /var/runtime/index.js",
    "trace": [
    "Runtime.ImportModuleError: Error: Cannot find module 'uuid'",
    "Require stack:",
    "- /var/task/src/todo/add.js",
    "- /var/runtime/UserFunction.js",
    "- /var/runtime/index.js",
    " at _loadUserApp (/var/runtime/UserFunction.js:100:13)",
    " at Object.module.exports.load (/var/runtime/UserFunction.js:140:17)",
    " at Object.<anonymous> (/var/runtime/index.js:43:30)",
    " at Module._compile (internal/modules/cjs/loader.js:1158:30)",
    " at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)",
    " at Module.load (internal/modules/cjs/loader.js:1002:32)",
    " at Function.Module._load (internal/modules/cjs/loader.js:901:14)",
    " at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)",
    " at internal/main/run_main_module.js:18:47"
    ]
    }

    Request ID:
    "d248d9de-bb1c-4923-87c3-e4530eae4c0e"

    Function Logs:
    START RequestId: d248d9de-bb1c-4923-87c3-e4530eae4c0e Version: $LATEST
    2020-04-18T01:05:43.836Z undefined ERROR Uncaught Exception {"errorType":"Runtime.ImportModuleError","errorMessage":"Error: Cannot find module 'uuid'\nRequire stack:\n- /var/task/src/todo/add.js\n- /var/runtime/UserFunction.js\n- /var/runtime/index.js","stack":["Runtime.ImportModuleError: Error: Cannot find module 'uuid'","Require stack:","- /var/task/src/todo/add.js","- /var/runtime/UserFunction.js","- /var/runtime/index.js"," at _loadUserApp (/var/runtime/UserFunction.js:100:13)"," at Object.module.exports.load (/var/runtime/UserFunction.js:140:17)"," at Object.<anonymous> (/var/runtime/index.js:43:30)"," at Module._compile (internal/modules/cjs/loader.js:1158:30)"," at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)"," at Module.load (internal/modules/cjs/loader.js:1002:32)"," at Function.Module._load (internal/modules/cjs/loader.js:901:14)"," at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)"," at internal/main/run_main_module.js:18:47"]}
    END RequestId: d248d9de-bb1c-4923-87c3-e4530eae4c0e
    REPORT RequestId: d248d9de-bb1c-4923-87c3-e4530eae4c0e Duration: 518.65 ms Billed Duration: 600 ms Memory Size: 1024 MB Max Memory Used: 23 MB
    Unknown application error occurred
    Runtime.ImportModuleError
    2020.04.18 10:11 신고
  • 프로필사진 신규하 package.json에 uuid가 있는지 확인해 봐 주세요.
    전역으로 설치 되어 있는 경우 그럴수 있을것 같습니다.

    추가로 사용하시는 node의 버전이 어떻게 되시나요?
    되도록이면 aws의 node 버전과 맞춰서 작업해 주세요.
    2020.04.18 20:34
  • 프로필사진 wjlim 안녕하세요^^
    작가님 덕분에 드디어 18장을 마무리 하였습니다! 정말 감사합니다!
    한가지 여쭤보고 싶은 것이 있습니다.
    aws rds 생성하고 연결까지 마쳤습니다.
    배포도 잘 되었습니다.

    다만 endpoint로 플레이그라운드 접속을 하려니 error가 나서 cloudwatch를 통해 로그를 확인하였습니다.
    "errorMessage": "ER_BAD_DB_ERROR: Unknown database 'board'",
    아마도 board라는 db가 인스턴스에 없어서 생긴 것 같습니다.

    직접 db를 생성하면 되겠지만 혹시 코드로 해결 할 수 있을까요?
    board라는 db가 없으면 db를 생성하고, db가 있으면 그냥 연결만 하라고 코드를 짜면 좋을 것 같은데,
    어디를 어떻게 바꾸면 될까요?

    소중한 답변 부탁드립니다^^
    2020.05.05 19:05
  • 프로필사진 신규하 먼저 18장까지 모두 보셨다니.. 축하드리고 감사합니다...

    typeorm의 cli를 통해서 database를 생성할 수는 있습니다.
    또 다른 방법으로는 자동으로 데이터베이스를 생성하는 방법이 아니라.. 마이그레이션 파일을 만들고 그 안에 queryRunner를 통해서 데이터베이스를 생성하시면 됩니다.

    이 답변이 도움이 되었으면 좋겠네요.
    그럼 좋은 하루 보내세요~
    2020.05.06 12:11
  • 프로필사진 gangbeat 안녕하세요.
    책구매해서 잘 보고 있습니다.
    쉽게 설명이 잘 되어 있어서 많은 도움이 되고 있습니다.
    감사합니다.
    2020.06.30 11:15
  • 프로필사진 신규하 책을 잘 봐 주신다니..
    너무너무 감사합니다.
    책 보시다가 궁금하신 점이 있으면 댓글로 달아 주세요. 성심것 답변 달아 드리겠습니다.
    감사합니다.
    2020.06.30 14:50
댓글쓰기 폼
공지사항
Total
1,737,453
Today
24
Yesterday
324
«   2020/08   »
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31