코드스테이츠_Devops_4기/section1) 서비스 운영기초, 개발 및 배포

Section1 실습과제 Day 2

불여우의 길 2023. 4. 5. 14:32

[WAS 실습]

 

Day2

Achievement Goal

- Fastify를 이용해 DB와 통신하는 서버 만들기

- PostgreSql을 이용하여 DB를 구성

 

Bare Minimum

- CRUD 기능을 가진 API 서버 환성

 

어제에 이어서 LMS 앱 애플리케이션을 만들게 되었다.

 

일단 어제 작성한 데이터 베이스는 조금 수정했다.

users 테이블에서 type 으로 구분시키던 학생과 강의자를 teachers 테이블을 만들어 구분하였다.

 

1. 데이터베이스 생성

Elephant SQL에서 새로운 데이터베이스를 하나 만들어주었다.

2. 테이블, 데이터 생성 

그리고 테이블을 만들었다.

--drop table users;
--drop table class;
--drop table teacher;
--drop table course;


-- 테이블 생성
CREATE TABLE USERS (
	ID INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
	NAME VARCHAR NOT NULL,
	EMAIL VARCHAR,
	PHONE VARCHAR 
);

CREATE TABLE CLASS (
	ID INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
	TITLE VARCHAR NOT NULL,
	TEACHER VARCHAR NOT NULL,
	CODE INTEGER NOT NULL,
	TEACHER_ID INTEGER NOT NULL
);

CREATE TABLE TEACHERS (
	ID INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
	NAME VARCHAR NOT NULL,
	EMAIL VARCHAR,
	PHONE VARCHAR 
);

CREATE TABLE COURSE (
	ID INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
	STUDENT_ID INTEGER NOT NULL,
	CLASS_ID INTEGER NOT NULL
);

-- 데이터 생성

INSERT INTO public.users ("name", email, phone) VALUES('길경서', 'gs@codestates.com', '01011111111');
INSERT INTO public.users ("name", email, phone) VALUES('김지연', 'jy@codestates.com', '01022222222');
INSERT INTO public.users ("name", email, phone) VALUES('박나혜', 'nh@codestates.com', '01033333333');

INSERT INTO public.teachers ("name", email, phone) VALUES('투명인간', 'inzisible@codestates.com', '01044444444');
INSERT INTO public.teachers ("name", email, phone) VALUES('도라에몽', 'emong@codestates.com', '01055555555');

INSERT INTO public."class" (title, teacher, code, teacher_id) VALUES('네트워크기초', '투명인간', 100, 1);
INSERT INTO public."class" (title, teacher, code, teacher_id) VALUES('데이터베이스', '투명인간', 200, 1);
INSERT INTO public."class" (title, teacher, code, teacher_id) VALUES('REST_API', '도라에몽', 300, 2);
INSERT INTO public."class" (title, teacher, code, teacher_id) VALUES('리눅스', '도라에몽', 300, 2);

INSERT INTO public.course (student_id, class_id) VALUES(1, 1);
INSERT INTO public.course (student_id, class_id) VALUES(1, 4);
INSERT INTO public.course (student_id, class_id) VALUES(2, 2);
INSERT INTO public.course (student_id, class_id) VALUES(2, 1);
INSERT INTO public.course (student_id, class_id) VALUES(3, 3);

이렇게 테이블도 만들고 데이터도 넣어주었다.

 

3. fastify 프로젝트 생성

그리고 fastify 프로젝트를 생성하였다.

fastify generate LMS

npm install --global fastify-cli (전역 사용)

// npm install 
npm i

우리팀은 각자 만들어보고 한 사람의 프로젝트를 git clone하여 github로 관리하면서 사용했다.

 

4. fastify 프로젝트에 만들어둔 데이터베이스 연동

.env 파일에 DB 정보를 작성하였다.

DATABASE_USER=dxplpxvl
DATABASE_PASSWORD=RoKKvOP86ZaSvwbt9D041lUJO4jJml66
DATABASE_NAME=dxplpxvl
DATABASE_HOST=floppy.db.elephantsql.com

 

5. 서버 실행 후 브라우저로 접속하기

 

실제 과제를 할 때는 쉘에서 진행했지만..ㅎㅎ

이젠 vscode에 멀티패스 서버를 연동시켜 두어서 vscode에서도 서버 실행이 가능하다.

vscode ssh 접속도 헤매는 바보라서 포스팅해봤다.ㅎㅎ

vscode ssh 접속방법 알아보기👈클릭

 

VSCode에 ssh로 접속하여 멀티패스 서버 연동하기

수업을 들으면서 멀티패스로 vi로 열어서 코딩하다보니까 아.. 나도 툴로 개발하고싶다... 생각하다가 결국 맥북을 사고싶어졌다. 하지만 살 수 없으니 VS Code에 멀티패스 서버를 연동해야겠다고

itfirefox.tistory.com

fastify는 외부접속을 허용하지 않기 때문에

FASTIFY_ADDRESS=0.0.0.0 npm run dev

명령어를 사용하여 외부접속이 가능하게 해주었다.

그리고 서버의 IP주소로 접속하였다.

 

지금은 local에서 ssh 접속을 허용시켜놨기 때문에 npm run dev 로 실행해서 localhost:3000으로 접속하는게 가능해졌다.

localhost:3000 과 localhost:3000/example로 접속하면 위와 같은 화면이 나온다.

이제 구조를 파악하고 비슷한 화면을 만들어보자

 

먼저 app.js를 살펴보자

// 스크립트를 엄격모드로 실행하겠다.
// 코드의 안정성과 가독성을 높여줌
'use strict'

// 모듈이나 패키지 로드

// 파일 경로를 조작하는 메소드 제공
const path = require('path')
// 필요한 라우트,스티마 등을 자동 로그할 수 있음.
const AutoLoad = require('@fastify/autoload')

// 옵션 객체를 외부에서 참조할 수 있도록 내보내기
// 외부에서 options라는 모듈을 require해서 사용할 수 있다.
module.exports.options = {}

module.exports = async function (fastify, opts) {

 
  fastify.register(AutoLoad, {
    dir: path.join(__dirname, 'plugins'),
    options: Object.assign({}, opts)
  })

  // This loads all plugins defined in routes
  // define your routes in one of these
  fastify.register(AutoLoad, {
    dir: path.join(__dirname, 'routes'),
    options: Object.assign({}, opts)
  })
}

app.js는 fasfify 서버 애플리케이션의 진입점 역할을한다.

모든 애플리케이션 실행과 관련된 설정과 라우트를 정의한다.

fastify 프로젝트는 이렇게 사용하세요.. 정도의 파일같다.

맞나..?

암튼

root.js, example.js 를 살펴보면 이렇다

'use strict'

module.exports = async function (fastify, opts) {
  fastify.get('/', async function (request, reply) {
    return { root: true }
  })
}
'use strict'

module.exports = async function (fastify, opts) {
  fastify.get('/', async function (request, reply) {
    return 'this is an example'
  })
}

module.export는 node.js에서 모듈을 내보내기하는 객체라고한다.

다른 모듈에서 require로 불러 사용가능하다.

따라서 둘다 get 메소드를 사용하여 객체와 문자열을 반환하고 있다.

그래서 문자열을 반환하는 화면이 나오도록 서버를 구성했다.

routes에 내가 원하는 엔드포인트로 디렉토리를 (nh) 만들어주었다.

'use strict'

module.exports = async function (fastify, opts) {
  fastify.get('/', async function (request, reply) {
    return 'I am parknahye'
  })
}

그리고 index.js를 만들어 위와같이 코드를 작성하였다.

요로케 출력되었다.

 

이런식으로 이번 과제였던 API를 호출하는 서버를 만들어보았당~

 

- GET / class

모든 수업을 조회

module.exports = async function (fastify, opts) {
  fastify.get('/', async function (request, reply) {
    //fastify-postgres에서 생성한 pg 인스턴스에서 데이터베이스 서버에 연결
    const client = await fastify.pg.connect()
    try {
        const { rows } = await client.query(
        'SELECT * FROM USERS'
        )
        reply.code(200).send(rows)
    } finally {
        client.release()
    }

  })

이런식으로 GET 메소드를 사용하여 데이터 베이스를 조회하는 api 서버를 만들었다.

- GET /class

- GET /class?code={code}

- GET /class?teacher={teacher}

- GET /class?title={title}

조건에 맞는 수업 조회

module.exports = async function (fastify, opts) {
fastify.get('/', async function (request, reply) {
    const client = await fastify.pg.connect()
    try {
      let rows;
      if (request.query.code) {
        // code 값이 존재하면 해당 코드와 일치하는 데이터만 반환
        const result = await client.query(
          'SELECT * FROM class WHERE code = $1',
          [request.query.code]
        )
        rows = result.rows;
      }else if(request.query.teacher){
  const result = await client.query(
          'SELECT * FROM class WHERE teacher = $1',
          [request.query.teacher]
        )
        rows = result.rows;
   }else if(request.query.title){
  const result = await client.query(
        'SELECT * FROM class WHERE title = $1',
          [request.query.title]
  )
   rows = result.rows;
   }else{
        // code 값이 없으면 전체 데이터 반환
        const result = await client.query('SELECT * FROM class')
        rows = result.rows;
      }
      reply.code(200).send(rows)
    } finally {
      client.release()
    }
})

이렇게 쿼리를 사용해서 request.query.컬럼 으로 파라미터를 받아서 SQL문을 사용하여 조회할 수 있었다.

대충 코드 설명은 주석으로 했당.. 하하

/class?code=200 /class?title=리눅스

- POST /class

새로운 강의 생성

fastify.post('/', async function(request, reply) {
    //const token = request.headers.authorization.split('')[1]
    const client = await fastify.pg.connect()
    try{
      let content = request.body;
      const result = await client.query(
        'INSERT INTO CLASS (title, teacher, code, teacher_id) VALUES ($1, $2, $3, $4)',
        [content.title, content.teacher, content.code, content.teacher_id]
      )
      reply.code(201).send('Created')


      }
      /*
      catch(err){
        console.error(err)
        reply.code(403).send('Forbidden')
      }
      */
    finally {
        client.release()
    }
})

post는 fastif.post~ 이런식으로 작성할 수 있었다.

post는 데이터를 보내줘야 생성할 수 있기 때문에 postman으로 api 전문을 만들어서 테스트 해보았다.

전문에 작성된 데이터가 update 된 걸 볼 수 있었다.

- PUT /class?id={id}

 
fastify.put('/', async function (request, reply) {
  const client = await fastify.pg.connect()
  try {
    let rows;
    if (request.query.id) {
      let content = request.body;
      const result = await client.query(
        'UPDATE CLASS SET TITLE=$2, TEACHER=$3, CODE=$4, TEACHER_ID=$5 WHERE id = $1',
        //request에서 쿼리로 보내는 id와 request body로 보내는 데이터터
        [request.query.id, content.title, content.teacher, content.code, content.teacher_id]
      )
    }
    reply.code(201).send('Updated')
  }finally {
    client.release()
  }


})

put도 동일하게 request.query에서 전달하는 id 값과 request.body로 전달하는 값들로 update 쿼리를 작성하여 파라미터들을 넣어 update 시켜줄 수 있다.

 

- DELTE /course?id={id}

  fastify.delete('/', async function(request, reply) {
    const client = await fastify.pg.connect()
    try{
      const result = await client.query(
        'DELETE FROM COURSE WHERE ID=$1;',
        [request.query.id]
      )
      reply.code(200).send('Deleted')
      }
    finally {
        client.release()
    }
  })

delete도 마찬가지로 request.query로 전송된 id 값으로 쿼리를 만들어 삭제시켜 주었다.

 

다른 api도 많지만 메소드별로 하나씩만 작성하였다.ㅎㅎ

 

여기에 원래 api라면 request heaser에 token값을 전달하여 인증이 성공하면 api를 수행하고 아니면 에러를 내야하는데,

이건 시도하다가 포기했당ㅋㅋ

 

이것까지는 개발 하지 않아도 괜찮다고 하셨지만 조금 욕심이 난다.

나중에 한번 해봐야징..

 


오늘의 회고

 

사실은 폴댄스 다녀와서 쓰려고했으나 삘받아서 폴 오래타고 피곤해서 그냥 중간정도 쓰다가 잤다.ㅎㅎㅎ

그래서 다음날 쓰고 있지만..

 

암튼 걱정했던것과는 다르게 꽤나 수월하게? 프로젝트를 마무리했다.

원래는 저녁까지 할 줄 알았는데 구조를 이해하고 보니 다른 메소드도 작성하는게 쉬웠다.

 

맨처음 코드를 작성할때는 fastify 프로젝트의 js 코드 형식이 낯설어서 어려웠지만, 이제 다른 api를 만들어보라고하면 다 할 수 있을 것 같다. (사실은 gpt의 멱살을 몇번 잡았지만)

 

하나의 숙제는 토큰인데..

토큰을 금방 완성할줄 알고 시도했다가 완벽한 코드에서도 제대로 돌지 않아서 포기했당ㅋㅋ

사실 여기서 토큰을 만들어서 프로젝트를 완성하는건 학습의 방향성에서는 약간 벗어나지만 요즘 처럼 jwt가 중요한 시기라면? 아예 진짜 JWT 토큰을 구현하는게 더 좋지않을까..

한번정도는 경험을 하고 넘어가는게 좋을 듯 하다.

시간이 되묜..? 사실은 귀찮지 않으면..? 인증토큰 부분을 짜봐야지

오늘하루도 사실은 어제지만 잘 끝맞췄다.