Cross-Origin Resource Sharing 의 줄임말로 서로 출저가 다르더라도 크로스 되어 요청에 대한 응답이 가능토록 하는 것으로 요청 시 지켜야하는 정책
ex. (Daum 브라우저에서 요청한 것을 네이버 백엔드에서 응답)
등장배경
웹이라는 오픈스페이스 환경에서 다른 출처에 있는 리소스를 가져와서 사용하는 것은 굉장히 흔한 일인데 SOP 정책으로는 이런 것이 불가능하기에 특정 조항만 지키면 출처가 다르더라도 요청을 허용하기로 해서 만들어진 것이 [CORS 정책]
preflight
을 통해 미리 요청을 보내서 요청이 가능한지 여부를 체크할 수 있음CORS 정책을 위반하는 요청을 할 경우 요청이 거부되는 데 이는 사실 서버가 아닌 브라우저에서 거부한 것
기본적으로 서버는 정책을 위반하는 요청에도 정상적으로 응답을 하지만, 해당 응답을 받은 후 브라우저에서 응답을 분석한 후 CORS 정책 위반이라고 판단되면 그 응답을 사용하지 않고 폐기해 버림
(즉, 요청 거부는 서버가 아닌 브라우저에서 동작되는 것!)
[이 경우 프록시 서버를 이용해서 요청에 대한 응답을 받아올 수 있음]
ex.
Naver(프론트) => [요청보냄] => Naver(백엔드서버) => [요청보냄] => Daum(백엔드서버)
위와 같이 백엔드로 요청을 보내고 해당 백엔드가 다시 본래 요청할 곳인 백엔드 서버로 요청을 보내 응답을 우회적으로 받아올 수 있음
이 때 대신해서 요청해주는 백엔드 서버를 [Proxy(프록시)서버] 라고 함
DataBase = 데이터를 담아두는 저장소
-DataBase 에 접근해서 무언가를 가져오려면 DB가 실행되어 있어야 하며 해당 DB에 접속을 해서 가져올 수 있음!
-이 때 DB 접속(통신)을 도와주는 툴이 ODM
(Object Document Mapping), ORM
(Object Relation Mapping)
ORM
툴 사용ODM
툴 사용
본래 javascript 는 브라우저에서만 작동 가능
javascript를 더 활용하기 위해 브라우저 외에서도 실행이 되도록 할 필요가 있었고, 이를 위해 만들어진 것이 node.js
사전에 node.js 를 설치했다면 터미널에서 node [파일명]
명령어를 입력하여 터미널 상에서 js 파일을 로드할 수 있음
따라서 node는 javascript 실행기이고, 타입스크립트를 실행하기 위해서는 TS 실행 프로그램인 ts-node
의 설치가 필요함
[BUT!] => ts-node
설치 후 해당 명령어로 실행 시 [Command not found] 라는 오류가 발생
typeORM
을 사용해 데이터베이스(DB) 와 백엔드 연결 가능
yarn add typeorm // typeorm 설치
yarn add pg // pg 설치 (typeorm 이 postgres와 연결하기 쉽게 돕는 라이브러리)
1. 데이터베이스(DB) 와 백엔드 연결
// [index.ts]
import { DataSource } from "typeorm";
import { Board } from "./Board.postgres"; //table 만든 파일
const AppDataSource = new DataSource({
type: "postgres",
host: "34.64.244.122", // DB가 있는 컴퓨터의 IP 주소
port: 5014, // DB가 있는 컴퓨터의 port
username: "postgres",
password: "postgres2022",
database: "postgres",
entities: [Board], // entity가 [ ] 여기 안에 들어감!
synchronize: true,
logging: true,
});
AppDataSource.initialize()
.then(() => {
console.log("연결 성공!");
})
.catch((error) => console.log(error, "연결 실패!"));
+a) entities["./*.postgres.ts"]
-> 해당 위치의 postgres.ts 로 끝나는 모든 파일들을 데이터베이스(DB) 와 연결시켜 주라는 뜻
2. 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;
}
여기 작성하는 부분이 테이블 형식이 됨
3. DBeaver 연동
사용하는 SQL 클릭
Host 부분에 백엔드 주소 입력
*localhost는 내 컴퓨터를 의미, 현재 컴퓨터에 백엔드가 없기에 백엔드로 접속가능하도록 백엔드 주소를 입력
test Connection 을 눌러서 정상적으로 연결되는지 확인
apollo-server
설치yarn add graphql // graphql 설치
yarn add apollo-server // apolloServer 설치
1. resolver(API)
2. typeDefs(API Docs)
앞서 Database와 연결한 index.ts
파일 안에 apollo-server 를 import 해와서 해당 코드를 추가 함
ApolloServer 설정 코드 (서버 생성 코드)
[관련 DOCS] = https://www.npmjs.com/package/@apollo/server?activeTab=readme
~~~
데이터베이스 연결하는 코드
~~~
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
// API Docs 만들기 [playground에서 볼 수 있는 API Docs]
const typeDefs = `#graphql
type Query {
hello: String
}
`;
// API 만들기 [실제 실행하는 함수 (playground 에서 작성할 때의 방식)]
const resolvers = {
Query: {
// 해당 api를 요청하면 아래의 함수가 실행됩니다.
hello: () => 'world',
},
};
// ApolloServer를 생성합니다.
// 위에서 만든 Docs(typeDefs)와 API(resolvers)를 넣어줍니다.
const server = new ApolloServer({
typeDefs,
resolvers,
});
// 서버를 열어두고 접속을 기다립니다.
// 아래 함수의 인자로 server 를 넣으면 생성해 둔 ApolloServer가 실행 됨
startStandaloneServer(server).then(() => {
console.log(`🚀 GraphQL 서버가 실행되었습니다.`); // 기본 port는 4000입니다.
});
~~~
데이터베이스 연결하는 코드
~~~
AppDataSource.initialize()
.then(() => {
console.log("DB 연결 성공!");
startStandaloneServer(server).then(() => {
console.log(`🚀 GraphQL 서버가 실행되었습니다.`); // port: 4000
});
})
.catch((error) => console.log(error, "DB 연결 실패ㅜ"));
startStandaloneServer(server).~~~
코드의 위치를 위와 같이 옮겨 줌 //Board.postgres.ts 파일(table 설정 파일)에서 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;
}
BaseEntity
를 사용하고 있는지 우선 확인! import { Board } from "./Board.table";
// API 만들기
const resolvers = {
Query: {
fetchBoards: async (parent: any, args: any, context: any, info: any) => {
// 모두 꺼내기
const result = await Board.find();
// 한개만 꺼내기
// const result = await Board.findOne({
// where: { number: 3 },
// });
return result;
},
},
Mutation: {
createBoard: async (_: any, args: any) => {
await Board.insert({
...args.createBoardInput,
// 하나 하나 모두 입력하는 비효율적인 방식
// writer: args.createBoardInput.writer,
// title: args.createBoardInput.title,
// contents: args.createBoardInput.contents,
});
return "게시글 등록에 성공했어요!!";
},
// updateBoard: async () => {
// // 3번 게시글을 영희로 바꿔줘!
// await Board.update({ number: 3 }, { writer: "영희" });
// },
// deleteBoard: async () => {
// await Board.delete({ number: 3 }); // 3번 게시글 삭제해줘!
// await Board.update({ number: 3 }, { isDeleted: true }); // 3번 게시글 삭제했다 치자! (소프트삭제) => isDeleted가 초기값인 false 이면? 삭제 안된거, true 이면? 삭제 된거
// await Board.update({ number: 3 }, { deletedAt: new Date() }); // 3번 게시글 삭제했다 치자! (소프트삭제) => deletedAt이 초기값인 NULL 이면? 삭제 안된거, new Date() 들어가 있으면? 삭제 된거
// },
},
};
// resolvers
const resolvers = {
Query: {
fetchBoards: async () => {
const result = await Board.find();
console.log(result);
return result;
},
},
};
Board.find()
로 import 해 온 Board(테이블 설정한 파일) 에서 모든 데이터를 찾아서 가져올 수 있음
# typeDefs
const typeDefs = `#graphql
# 객체의 타입을 지정해줍니다.
type MyBoard {
writer: String
number: Int
title: String
contents: String
}
type Query {
# // 결과가 여러개이므로 배열에 담아서 보내줍니다.
# // GraphQL에서는 배열 안의 객체를 [객체]로 표기합니다.
fetchBoards: [MyBoard]
}
`;
typeDefs 안에는 API-DOCS 를 만들어 줌
이 때 값을 조회(fetch)하는 경우 type
으로 지정해 주고, 값을 입력하는 경우 input
으로 지정해 줌
*위의 경우 type을 MyBoard로 작성 후 그 값을 fetchBoards 안에 넣어 줌 (코드의 간결화)
// resolvers
const resolvers = {
Query: {
fetchBoard: async () => {
const resultOne = await Board.findOne({
where: { number: 3 },
});
return resultOne;
},
},
};
findOne( )
을 사용하고, 조건을 추가해줘야 함where : {}
을 사용해 어떤 값을 가져올지를 정확히 지정
// 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 "게시글 등록에 성공했습니다.";
},
},
};
Mutation
안에 작성
# 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
}
`;
// resolvers
const resolvers = {
Mutation: {
updateBoard: async () => {
// update(조건, 수정할내용)
// 👇🏻 3번 게시글을 영희로 바꿔줘!
await Board.update({ number: 3 }, { writer: "영희" });
},
},
};
Mutation 안에 조건, 수정할 내용을 알맞게 작성
typeDefs도 마찬가지로 작성
// resolvers
const resolvers = {
Mutation: {
deleteBoard: async () => {
// delete(삭제할 조건)
// 👇🏻 3번 게시글을 삭제해줘!
await Board.delete({ number: 3 });
},
},
};
// resolvers
const resolvers = {
Mutation: {
deleteBoard: async () => {
await Board.update({ number: 3 }, { deletedAt: new @Column({ type: "timestamp", default: null, nullable: true })
deletedAt?: Date;Date() });
},
},
};
// 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;
}