2023. 4. 4 ~ 4.5

Junghan Lee·2023년 4월 5일
0

TIL Diary

목록 보기
31/52

INDEX

Intro
1) openAPI끌어오기
2) DataBase(DB) - SQL, NoSQL
3) DB 관리 프로그램, DBeaver
4) 백엔드 서버 구축(Node.js에서 js실행, typescript실행, 프로젝트&DB연결(typeORM, DB-백엔드 연결, entity)
5) DBeaver연동
6) ApolloServer 세팅(설치, 기본API틀 생성, 서버 열어두기)
7) 웹서비스 구조(프론트엔드 서버)
8) 게시판 CRUD만들기(조회API-모두/한개,생성API, 수정API,삭제API)
9) Firebase BAAS 서비스 (개념,프로젝트&DB와 연결)


INTRO

4월 4일 ~ 5일 (2일간) 백엔드에 대해 빠르고 간략히 학습할 것이다.

프론트엔드라고 백엔드를 몰라도 되겠어?

서버의 기본적인 구조
브라우저 - 백엔드(DB에서 꺼내와 브라우저의 query등 요청에 응답) / 프론트엔드(yarn dev, 브라우저에 html, css, js 제공) - 데이터베이스

decorator?
expressor, apollo-server?

openAPI의 CORS는 무엇일까? Cross Origin Resource Sharing 네이버에서도 다음 백엔드, 다음에서도 네이버 백엔드 데이터를 공유할 수 있게 하자!
cors: ['다음', '쿠팡'] 해놓으면 다음, 쿠팡에만 응답 보내줌

SOP(Same Origin Policy) 네이버는 네이버 백엔드, 다음이면 다음 백엔드

preflight 보내도 돼? 물어보는 것

cors 우회와 프록시 서버

CORS(Cross-Origin Resource Sharing)는 웹 애플리케이션에서 보안상의 이유로 다른 도메인으로부터 리소스 요청을 할 때 발생하는 보안 정책이다. 이러한 정책 때문에 도메인이 다른 웹 애플리케이션 간에 데이터를 주고받는 것이 제한된다.

하지만, 때로는 다른 도메인으로부터 리소스를 가져와야 하는 경우가 있다. 이런 경우에는 Proxy 서버를 활용하여 CORS를 우회할 수 있다. Proxy 서버는 클라이언트와 서버 간에 중계 역할을 수행하는 서버로, 클라이언트는 Proxy 서버에 요청을 보내고, Proxy 서버는 요청을 받아 다시 서버에 요청을 보내고, 서버로부터 응답을 받아 클라이언트에게 전달하는 방식으로 동작한다.

Proxy 서버를 활용하여 CORS를 우회하는 방법은 다음과 같다.

1) 클라이언트는 Proxy 서버에 요청을 보냄
2) Proxy 서버는 서버에 요청을 보내고, 서버로부터 응답을 받는다.
3) Proxy 서버는 서버로부터 받은 응답을 클라이언트에게 전달
이렇게 Proxy 서버를 활용하여 클라이언트는 Proxy 서버를 통해 서버와 통신하므로, CORS 정책을 우회할 수 있다.

대표적인 Proxy 서버로는 nginx, Apache, Node.js 등이 있다. 이러한 Proxy 서버를 활용하여 CORS를 우회하는 방법은 간단하지만, 보안상 주의할 점이 있다. Proxy 서버를 사용할 때, 보안상 취약점을 찾아내고 보호해야 한다. 또한, Proxy 서버를 사용하여 CORS를 우회하면 서버와의 통신이 중간에 제3자를 거쳐 이루어지므로, 보안상 문제가 발생할 가능성이 있다. 따라서, Proxy 서버를 사용할 때에는 보안상 주의를 기울여야 한다.

브라우저의 쿠키
브라우저의 쿠키(Cookie)는 웹 사이트의 서버가 클라이언트 측에 저장하는 작은 데이터 파일이다. 쿠키는 클라이언트 측의 웹 브라우저에 저장되어, 해당 웹 사이트를 방문할 때마다 웹 서버에서 쿠키를 읽어 해당 정보를 이용한다.

쿠키는 일반적으로 로그인 정보, 사용자 환경설정, 쇼핑카트, 추천 상품 등을 저장하며 다음과 같은 방식으로 동작한다.

1) 클라이언트가 웹 사이트에 접속하면, 웹 서버는 클라이언트에게 쿠키를 생성하도록 지시.
2) 클라이언트는 웹 서버에서 받은 쿠키를 저장
3) 클라이언트가 같은 웹 사이트를 재방문하면, 저장된 쿠키를 웹 서버에 전송.
4) 웹 서버는 클라이언트의 쿠키를 읽어 해당 정보를 이용.

쿠키는 일반적으로 유효기간을 가지고 있으며, 유효기간이 만료되면 자동으로 삭제된다. 쿠키는 브라우저의 보안 설정에 따라 차단될 수 있으며, 브라우저에서 쿠키를 삭제하거나, 유효기간이 만료되면 자동으로 삭제된다.

쿠키는 보안상의 이유로 중요한 정보는 저장하지 않는 것이 좋다. 또한, 사용자의 개인정보나 민감한 정보를 저장할 때에는 반드시 암호화를 해야 한다. 브라우저에서는 개인정보를 포함한 쿠키를 차단하는 보안 기능도 제공하고 있으므로, 보안적인 측면에서도 적극적인 대응이 필요하다.

1. openAPI 끌어오기

openAPI로 새로운 페이지를 만드는 과정

// 첫 마운트시에만 요청을 보낼 수 있도록 의존성 배열을 같이 적어줍니다.
// open api가 rest-API이기 때문에 axios를 이용합니다.

useEffect(() => {
    const getImg = async (): Promise<void> => {
      // [1, 1, 1, 1, 1, 1, 1, 1, 1]
      new Array(9).fill(1).forEach(async (_) => {
        const result = await axios.get(
          "https://dog.ceo/api/breeds/image/random" // open API 주소
        );
        setImgUrls((prev) => [...prev, result.data.message]);
      });
    };
    void getImg();
  }, []);

💡왜 useEffect와 함께 사용해야 할까?
1) 브라우저가 실행되자 마자 요청을 날려주어야 함
2) state 변화 -> 화면 리렌더 // useEffect 사용하지 않고 그냥 요청을 하면 state가 변할 때마다 화면이 다시 렌더되어 불필요한 리렌더링 발생 -> 비효율적 -> 첫 마운트시에만 요청할 수 있도록 useEffect에 [] 넣어 사용

forEach & map
-> forEach는 배열의 각 요소에 대해 동일한 실행

array.forEach((value, index, array) => {
array[index] = value + 2;
});

cors 에러
-> openAPI 업체 측에서 보안상의 이유로 허용할 브라우저를 지정하게 되는데 허용하지 않은 브라우저에서는 요청을 보내지 못하고 cors 에러를 보게 된다. 예) 네이버브라우저 -> 카카오 백엔드에 요청(cors에러 발생)

브라우저에서 Open API 사용할 때는 auth가 No인 API를 사용해야 CORS에러를 피할 수 있으며 key를 발급받아 권한을 열어 사용하는 방법 또한 있다.(각각의 API마다 docs를 잘 읽고 활용)

DataBase

개념 : 데이터를 담아두는 저장소
담아두는 방식 : SQL, NoSQL

axios나 apollo-client처럼 백엔드에도 데이터베이스와 통신을 돕는 툴이 있고 이 툴은 담아두는 방식(SQL, NoSQL) 에 따라 달라짐 -> 툴 역시 ORM(object document mapping; sequalize, typeORM, prisma), ODM(object relation mapping;mongoose) 2가지가 존재

SQL 방식
데이터를 엑셀과 비슷한 표(table)에 정리해두는 방식
NoSQL 방식과 달리 각각의 표 사이에 관계성을 부여할 수 있다 -> mapping해주는 툴 ORM 사용
관계성을 부여하는 데이터베이스 = 관계형 데이터베이스(relational database: RDB)
대표적으로 Oracle, MySQL, Postgres 가 있다.
행(row) : ID,Data(key,value) 형식
열(column) : 각각 key 등
행렬구조(table)

select 제목, 내용 from Board where 작성자 = U01(ID) // 원래 이렇게 요청해야 게시글의 제목, 내용 정보 등을 DB에서 보내주지만 ORM 의 도움을 받으면.. Board.find() 하면 끝남

NoSQL 방식
SQL이 엑셀이라면 NoSQL은 서류 봉투에 documents 를 모아두는 방식, 서류 봉투를 컬렉션이라고 부르며 ODM 사용
대표적으로 MongoDB, FireBase, Redis 가 있다.
document : id, Data(key,value...)//field
서류봉투(collection)구조

ODM을 통해 Board.find() 하면 끝남(mongoDB; db.board.find()) 자동 변환

DB 관리 프로그램

데이터베이스에는 Postgres가 이미 설치되어 있는데 DB 관리 프로그램은 postgres안의 데이터를 좀 더 편하게 조회할 수 있도록 돕는 프로그램으로 DBeaver, MySQL, webpack 등이 있다.
DB관리 프로그램은 데이터베이스가 아님!

백엔드 서버 구축

Node.js에서 JavaScript 실행
node.js가 나오기 이전의 자바스크립트는 브라우저에서만 작동되었다. 그러나 자바스크립트를 통해 다른 것들을 개발하기 위해 브라우저가 아닌 다른 곳에서도 실행이 되도록 한 것이 Node.js로 이는 JavaScript 실행 프로그램이다. 어떻게 실행될까?

1) 파일 만들기

2) 간단한 명령어 만들기

3) 터미널에 node[파일] 입력해 파일 실행

Node.js에서 typescript실행
1) 파일 만들기

2) 터미널에 yarn add --dev typescript 입력해 타입스크립트 설치
3) tsconfig.json 파일 생성 후 파일의 내용을 https://www.typescriptlang.org/(docs) 보고 채우기(what is config.json)

{
  "compilerOptions": {
    "target": "ES2015",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "$schema": "https://json.schemastore.org/tsconfig",
  "display": "Recommended"
}

💡 TypeScript 설치와 설정을 완료했지만, 아직은 TypeScript 파일을 실행할 수 없다.

왜?

node는 자바스크립트 실행기이기 때문에 타입스크립트를 실행할 수 없기 때문.

따라서! 타입스크립트를 실행시켜줄 수 있는 TS 실행 프로그램인 ts-node를 설치

4) ts-node 설치 (yarn add ts-node)
5) package.json 에서 ts-node 이용해 index.ts 실행하는 명령어 추가

"scripts": {
    "명령어": "ts-node index.ts"
  },


6) 이제 위에서 지정한 명령어를 터미널에 입력하면 지정해둔 ts파일이 실행된다.(yarn 명령어)

프로젝트와 데이터베이스 연결
typeORM을 이용해 데이터베이스와 백엔드를 연결할 수 있다.
typeORM 설치
1) yarn add typeorm
2) yarn add pg (pg란 typeorm이 postgres와 연결하기 쉽게 돕는 라이브러리)

데이터베이스와 백엔드 연결
index.ts

import { DataSource } from "typeorm";
import { Board } from "./Board.postgres";

const AppDataSource = new DataSource({
  type: "postgres",
  host: "34.64.244.xxx", // DB가 있는 컴퓨터의 IP 주소
  port: 5014, // DB가 있는 컴퓨터의 port
  username: "postgres",
  password: "postgresxxxx",
  database: "postgres",
  entities: [Board],
  synchronize: true, // 동기화
  logging: true, // 코드 변경하는 것 보기
});

AppDataSource.initialize()
  .then(() => {
    console.log("연결 성공!");
  })
  .catch((error) => console.log(error, "연결 실패!"));

entities 파일 경로
-> entities["./*.postgres.ts"]
해당 위치의 postgres.ts로 끝나는 모든 파일들 데이터베이스와 연결시켜달라는 뜻

entity 만들기
Board.postgres.ts 파일 새로 만들기

// entities 만들어주기 _ 새로운 타입스크립트파일을 만들어 주세요
import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from "typeorm";

// @ -> 데코레이터(타입 오알엠에게 테이블임을 알려줍니다. 데코레이터는 함수입니다.)
 @Entity() 
	export class Board extends BaseEntity {
		// primaryGenerateColumn: 자동으로 생성되는 번호			
			@primaryGenerateColumn(’increment’) 
			number!: number;
			
			@column({type : “text”})
			wrtier!: string;
		
		  @Column({ type: "text" })
		  title!: string;
		
		  @Column({ type: "text" })
		  contents!: string;

}

오류가 많은 이유는 뭘까? 데코레이터의 타입

데코레이터의 타입을 tsconfig.json에서 설정해야 한다.

"experimentalDecorators": true

메타데이터만 있고 Board가 없다면?
연결은 되어있으나 데이터가 들어가지 않은 상황으로 index.ts의 entities확인 필요, 경로나 오타 확인

git push전 check!
.gitignore 직접 만들어야 함(next.js는 자동 생성)

DBeaver 연동


좌측 상단의 플러그 모양 클릭 후 postgreSQL 선택,
Host부분에 localhost대신 백엔드 주소(34.64.244.122) 입력, 비밀번호는 postgres2022
localhost는 내 컴퓨터인데 내 컴퓨터엔 백엔드가 없으므로 백엔드에 접속할 수 있도록 백엔드 주소 입력

모두 입력 후 test connection => 파란색이 뜨면 완료

Apollo Server 세팅

브라우저에서 API를 요청할 수 있도록 백엔드 서버를 24시간 열어두는 과정, graphql을 이용해 api 를 만들기 위해 apollo-server 설치 필요(rest-API는 express(koa)를 사용함)

ApolloServer 설치
yarn add graphql -> yarn add apollo-server

기본 API틀 생성, 서버 열어두기
resolver(API), typeDefs(API Docs) 가 필요하기 때문에 이 두가지를 만들고 열어야 함
https://www.npmjs.com/package/@apollo/server?activeTab=readme
위의 주소에서 제공하는 내용을 참조해 만들면 된다. 위에서 DB를 연결한 index.ts 파일에 아래의 코드를 추가한다. DB를 연결하는 코드를 지우지 말고 import 문 아래 나머지 코드 위에 추가한다.

~~~
데이터베이스 연결하는 코드
~~~

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';

// API Docs 만들기
const typeDefs = `#graphql
  type Query {
    hello: String
  }
`;

// API 만들기
const resolvers = {
  Query: {
		// 해당 api를 요청하면 아래의 함수가 실행됩니다.
    hello: () => 'world',
  },
};

// ApolloServer를 생성합니다.
// 위에서 만든 Docs(typeDefs)와 API(resolvers)를 넣어줍니다.
const server = new ApolloServer({
  typeDefs,
  resolvers,
});

// 서버를 열어두고 접속을 기다립니다.
startStandaloneServer(server).then(() => {
      console.log(`🚀 GraphQL 서버가 실행되었습니다.`); // port는 4000입니다.
    });

~~~
데이터베이스 연결하는 코드
~~~

데이터베이스가 연결되고 나서 서버를 실행하도록 startStandaloneServer~~ 코드의 위치를 아래처럼 옮겨준다.

AppDataSource.initialize()
  .then(() => {
    console.log("DB 연결 성공!");
    startStandaloneServer(server).then(() => {
      console.log(`🚀 GraphQL 서버가 실행되었습니다.`); // port: 4000
    });
  })
  .catch((error) => console.log(error, "DB 연결 실패ㅜ"));

여기서 열어둔 서버는 뭘까?

웹서비스 구조

웹서비스는 프론트엔드, 백엔드서버와 데이터베이스로 이루어져 있다.

서버? 24시간 켜져있는 서버 프로그램이 동작하고 있는 컴퓨터

프론트엔드 서버
yarn dev해서 실행시키면서 생성된 포트가 동작하고 있는 컴퓨터

프론트엔드 서버 프로그램 : yarn dev -> 포트 생성, 포트는 누군가 종료하지 않으면 24시간 동작하는데 24시간 켜져 있는 포트 번호를 서버 프로그램이라고 한다.

24시간 켜져있는 이유? 접속을 기다리기 때문, 바로바로 응답을 주기 위해서는 24시간 켜져 있어야 한다. 데이터베이스는 백엔드의 접속을 기다리고 프론트엔드 서버는 브라우저의 접속을 기다린다.

게시판 CRUD 만들기

테이블의 데이터를 삭제하고 추가하고 수정하는 일이 생기면 이런 기본 기능을 사용하기 위해 Board.postgres.ts파일로 돌아가 BaseEntity를 사용하고 있는지 먼저 확인해야 함

//Board.postgres.ts 파일에서 BaseEntity를 입력했는지 확인합니다.

import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from "typeorm";
 @Entity() 
	export class Board extends BaseEntity{	
			@PrimaryGeneratedColumn("increment")
		  number!: number;

			@Column({ type: "text" })
		  wrtier!: string;
		
		  @Column({ type: "text" })
		  title!: string;
		
		  @Column({ type: "text" })
		  contents!: string;

}

기본 틀을 가져온 index.ts 파일에서 진행

조회하기 API

모두 조회하기

// resolvers

const resolvers = {
  Query: {
    fetchBoards: async () => {
      const result = await Board.find();
      console.log(result);
      return result;
    },
  },
};

위의 typeDefs에서는 API-DOCS를 만들어준다.

# typeDefs

const typeDefs = `#graphql
  # 객체의 타입을 지정해줍니다.
  type MyBoard {
    writer: String
    number: Int
    title: String
    contents: String
  }
  type Query {
    # 결과가 여러개이므로 배열에 담아서 보내줍니다.
    # GraphQL에서는 배열 안의 객체를 [객체]로 표기합니다.
    fetchBoards: [MyBoard]
  }
`;

한개만 조회하기 : findOne() 사용, 조건 추가해야 함 ``` // resolvers

const resolvers = {
Query: {
fetchBoard: async () => {
const resultOne = await Board.findOne({
where: { number: 3 },
});
return resultOne;
},
},
};

typeDefs에 API-DOCS 만들기

**생성하기 API**
조회를 제외한 create, update, delete 는 Mutation 안에 작성,
args를 하나의 객체(createBoardInput)으로 묶어 받아오는 방식 사용

// resolvers

const resolvers = {
Mutation: {
// parent vs args: 브라우저의 요청은 args로 인자를 받고, 다른 api의 요청은 parent로 인자를 받습니다.
createBoard: async (parent: any, args: any, context: any, info: any) => {
await Board.insert({
/ 1. 연습용(backend-example 방식) /
// writer: args.writer,
// title: args.title,
// contents: args.contents,

    /* 2. 실무용(backend-practice 방식) */
    ...args.createBoardInput,
  });
  return "게시글 등록에 성공했습니다.";
},

},
};

인자로 들어가는 객체의 타입은 type이 아닌 Input으로 작성

typeDefs

const typeDefs = `#graphql

인자로 들어가는 객체의 타입은 type이 아닌 input으로 작성합니다.

input CreateBoardInput {
writer: String
title: String
contents: String
}
type Mutation {

# GraphQL에서는 문자열의 타입을 String으로 입력해주셔야 합니다.
# 필수값인 경우에는 콜론(:) 앞에 !를 붙여주세요.

# 1. 연습용(backend-example 방식)
# createBoard(writer: String, title: String, contents: String): String

# 2. 실무용(backend-practice 방식)
createBoard(createBoardInput: CreateBoardInput): String

}
`;


**수정API**
update(조건, 수정할 내용) 순서에 맞게 작성

// resolvers

const resolvers = {
Mutation: {
updateBoard: async () => {
// update(조건, 수정할내용)
// 👇🏻 3번 게시글을 영희로 바꿔줘!
await Board.update({ number: 3 }, { writer: "영희" });
},
},
};

typeDefs에 API-DOCS 만들기

**삭제 API**
보통 실무에서는 사용하지 않는다. 왜? data를 삭제하면 돌이킬 수 없기 때문에 실수 방지, 추후 데이터가 필요할 상황을 대비해 실제로 데이터를 지우는 경우가 거의 없다. 그래서 삭제 대신 Soft Delete라는 방식을 주로 사용

// resolvers

const resolvers = {
Mutation: {
deleteBoard: async () => {
// delete(삭제할 조건)
// 👇🏻 3번 게시글을 삭제해줘!
await Board.delete({ number: 3 });
},
},
};

위는 삭제(참고)

그렇다면 Soft Delete는?
삭제 여부를 담는 column(deletedAt)만들고 update를 이용해 삭제 여부 입력, 이 방법을 사용하면 데이터 조회시 deletedAt이 NULL인 행(IsNull())만 조회한다는 조건을 추가해야 한다.

// resolvers

const resolvers = {
Mutation: {
deleteBoard: async () => {
await Board.update({ number: 3 }, { deletedAt: new @Column({ type: "timestamp", default: null, nullable: true })
deletedAt?: Date;Date() });
},
},
};

typeDefs에 API-DOCS 만들어야 하고 Column 추가해준다.

// Board.postgres.ts

import { column, Entity, primaryGeneratedColum, BaseEntity } from "typorm"

@Entity()
export class Board extends BaseEntity{

		~~~
	
		@Column({ type: "timestamp", default: null, nullable: true })
	  deletedAt?: Date;

}


### <span style='color:orange'>Firebase BAAS 서비스

**Firebase란?**
프론트엔드 개발자가 백엔드 없이 데이터를 FireBase에 직접 넣을 수 있다.
![](https://velog.velcdn.com/images/creyon0215/post/886d4ceb-cee8-4e6b-afdf-e52e952f133d/image.png)

이를 이용하기 위해선 먼저 가입!
https://firebase.google.com/?gclid=Cj0KCQiAy4eNBhCaARIsAFDVtI1ikjT9TvPmnUF-pdwKyrU2BachcejZAK23-42fSYuyPRBbqbeWFJwaAqrMEALw_wcB&gclsrc=aw.ds

시작하기를 통해 새로운 프로젝트를 만들고 프로젝트와 firebase를 연결해야 한다.
![](https://velog.velcdn.com/images/creyon0215/post/3954d810-b489-4715-8a44-20d9f22ab396/image.png)
VSC에서 class프로젝트의 src>commons에 libraries생성 후 firebase.ts 만들기
복사한 내용을 파일 안에 붙여 넣는데 app이 app.tsx와 헷갈릴 수 있으니 firebaseAPp으로 바꾸고 다른 파일에서 사용할 수 있도록 export 를 붙인다.
![](https://velog.velcdn.com/images/creyon0215/post/891eb358-41f0-452a-a11b-99747e88da4b/image.png)

**Database 생성**
빌드 - Firestore DB로 접속, 데이터베이스 만들기 클릭해 데이터베이스 생성한다.
Cloud Firestore의 위치는 asia-northeast3

https://firebase.google.com/docs/firestore/query-data/get-data?hl=ko&authuser=0

DOCS 참조해 데이터 등록/ 조회 기능을 만든다!
class프로젝트에 firebase 설치
yarn add firebase@9.10.0

**등록, 조회 구현하기**

1) firebase/firestore/lite에서 필요한 것들을 Import 해 사용
2) 위에서 만든 파일의 firebaseApp import
3) 필요한 기능 구현

import {
collection,
addDoc,
getDocs,
getFirestore,
} from "firebase/firestore/lite";
import { firebaseApp } from "../../../src/commons/libraries/firebase";

export default function FirebasePage(): JSX.Element {
const onClickSubmit = (): void => {
const board = collection(getFirestore(firebaseApp), "board");
void addDoc(board, {
writer: "철수",
title: "안녕하세요",
contents: "반갑습니다",
});
};
const onClickFetch = async (): Promise => {
const board = collection(getFirestore(firebaseApp), "board");
const result = await getDocs(board);
const datas = result.docs.map((el) => el.data());
console.log(datas);
};
return (
<>
등록하기
조회하기
</>
);
}

collection 을 통해 firebase의 cloud storage에 해당하는 컬렉션을 지정해 addDoc을 통해 해당 컬렉션에 내용을 넣어주고 getDocs를 통해 저장된 내용들을 꺼내 불러올 수 있다.

위 파일을 실행해 데이터를 등록하면 파이어베이스에서 추가된 데이터를 확인할 수 있다.
profile
Strive for greatness

0개의 댓글