- ๐ ํ๋ก ํธ์๋(HTML ๋ฒํผ)์ axios ์ฐ๋ํ๊ธฐ
- ๐ Axios ์ฐ๋ํ๊ธฐ
- ๐ CORS ๋ฌธ์
- ๐ apollo-server ์ค์น
- ๐ GraphQl-API ๋ง๋ค๊ธฐ ์ค์ต
ํ๋ก ํธ์๋(HTML ๋ฒํผ)์ axios ์ฐ๋ํ๊ธฐ
์ค๋ Apollo
์๋ฒ๋ฅผ ์ฌ์ฉํ์ฌ Graphql-api
๋ฅผ ์ ๊ณตํ๋ ์๋ฒ๋ฅผ ๋ง๋ค์ด๋ณด์!!
apollo ์๋ฒ๋ graphql-api๋ฅผ ์ ๊ณตํ๋ ์๋ฒ๋ฅผ ๊ฐ๋ฐํ ์ ์๊ฒ ๋์์ฃผ๋ ํจํค์ง์ด๋ค.
๊ธฐ์กด์ ์ฌ์ฉํ๋ express์ ์ญํ ์ด ๋น์ทํ๋ค.
์ค์ต์ ํด๋ณด๊ธฐ ์ํด ์ฐ์ ๊ฐ๋จํ html๋ฌธ์๋ฅผ ๋ง๋ค์ด๋ณด์
ํด๋ํฐ ๋ฒํธ๋ฅผ ์
๋ ฅํ ๋ค, ์ธ์ฆํ๊ธฐ ๋ฒํผ์ ๋๋ฅด๊ฒ ๋๋ฉด, ์ธ์ฆ์ํ ๋ฌธ๊ตฌ๊ฐ ์ธ์ฆ์๋ฃ๋ก ๋ฐ๋๋ ๋ก์ง์ด๋ค.
์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก html ๋ฌธ์์ ์์ฑํ api๊ฐ ์ํต์ ํ ์ ์๋๋ก ํด๋ณด์!!
์ง๊ธ๊น์ง๋ postman์ ํตํด api๋ฅผ ํ
์คํธํด์๋ค.
์ด๋ฒ์๋ ํด๋ํฐ ๋ฒํธ๋ฅผ ๋ฐฑ์๋ ์๋ฒ๋ก ์์ฒญํ๊ธฐ ์ํด axios
๋ฅผ ์ฌ์ฉํด ๋ณด๋๋ก ํ๊ฒ ๋ค.
์ฐ์ HTML ํ์ผ์์ axios๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด <script>
ํ๊ทธ๋ฅผ ์ฌ์ฉํ์ฌ ๋ค์ด๋ก๋๋ฅผ ๋ฐ์์ผ ํ๋ค.
์ด๋ ๊ฒ ๋ค์ด๋ก๋ ๋ฐ๋ ๋ฐฉ์์ CDN
(contens delevery network)๋ผ ๋ถ๋ฅธ๋ค.
axios.cdn์ ๊ฒ์ํด๋ณด๋ฉด ๊ณต์๋ฌธ์์ ์๋์ ๊ฐ์ cdn ์ฝ๋๊ฐ ์์ผ๋ฉฐ, ์ด๊ฒ์ ์์ ๋งํ <script>
ํ๊ทธ ์์ ๋ฃ์ด์ค๋ค.
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
์ด์ axios๋ฅผ ์ฌ์ฉํ ์ค๋น๊ฐ ๋ชจ๋ ๋๋ฌ๋ค.
axios๋ฅผ ์ด์ฉํ์ฌ api๋ก ์์ฒญ์ ๋ณด๋ด๋ณด์
axios.post
: api ์์ฒญ ๋ณด๋ด๊ธฐ
axios.then
: ์์ฒญ์ ๋ณด๋ธ ํ, ์คํ๋๋ ์ฝ๋ ์์ฑํ๊ธฐ (์์ฒญ์ ๋ํ ์๋ต)// signup.html <head> <title>ํ์๊ฐ์ ์ฐ์ตํ๊ธฐ</title> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script> function check() { // 1. ์ ๋ ฅํ ํด๋ํฐ๋ฒํธ ๊ฐ์ ธ์ค๊ธฐ const myphone = document.getElementById('qqq').value; console.log('๋์ ํธ๋ํฐ๋ฒํธ: ', myphone); // 2. ํด๋น ํด๋ํฐ๋ฒํธ๋ก ์ธ์ฆ๋ฒํธAPI ์์ฒญํ๊ธฐ axios .post('http://localhost:3000/tokens/phone', { qqq: myphone, }) .then((res) => { console.log(res); document.getElementById("zzz").innerText = res.data }); } </script> </head>
์ด์ backend์์ ์๋ฒ๋ฅผ ์คํ์ํค๊ณ , ์์ฒญ์ ๋ณด๋ด๋ณด์
๊ทธ๋ฐ๋ฐ, ์ ๋๋ก๋ ์๋ต์ด ๋ํ๋์ง ์๋๋ค...
๊ฐ๋ฐ์ ๋๊ตฌ์ ์ฝ์์ฐฝ์ ํ์ธํด๋ณด๋ฉด ์ด์ ๋ฅผ ์ ์ ์๋ค.
์ฌ๋ฌ ์๋ฌ๋ค์ด ๋ํ๋๊ณ ์์ง๋ง ์ด ์ค, CORS
์ ๋ํด ์์๋ณด๊ธฐ๋ก ํ์!!
postman์์ ์ฑ๊ณตํ๋ ์์ฒญ๊ณผ ๊ฐ์ ์์ฒญ์ ์ง์ ๋ง๋ ๋ฒํผ์ ํ์ฉํ์ฌ ๋ธ๋ผ์ฐ์ ์์ ์งํํ๋๋ฐ ์ ์๋ฌ๊ฐ ๋ฐ์ํ๊ฑธ๊น?
์ด์ ๋ CORS
๋๋ฌธ์ด๋ค.
CORS๋ (cross origin resources sharing)์ ์ฝ์๋ก ์๋ก๋ค๋ฅธ ์ถ์ฒ(origin)๋ฅผ ๊ฐ์ง ์ฃผ์๋ก ์์ฒญ์ด ๋ค์ด์์ ๋, ๋ฐ์ํ๋ ์๋ฌ์ด๋ค.
์ฌ๊ธฐ์ ์ถ์ฒ๋ portq๋ฒํธ๊น์ง ํฌํจํ url์ ์๋ฏธํ๋ค.
์๋ฅผ ๋ค์ด http://localhost:3000/tokens/phone)
์์ http://localhost:3000
๋ถ๋ถ์ origin์ด๋ผ ํ๋ค.
port ๋ฒํธ๊ฐ ๋ค๋ฅธ ๊ฒฝ์ฐ์๋ CORS๊ฐ ๋ฐ์ํ๋๋ฐ, ์ด์ ๋ ๊ฐ์ localhost์ด๋๋ผ๋ ๋ค๋ฅธ ์ถ์ฒ๋ก ์ธ์๋๊ธฐ ๋๋ฌธ์ด๋ค.
๋ฐ๋ผ์, CORS๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์๋ ๋ฐฑ์๋ ์๋ฒ์ ์๋ต ํค๋์ accss-control-allow-origin
ํญ๋ชฉ์ ํ์ฉํ๊ณ ์ํ๋ origin์ ๋ด์ CORS ์๋ฌ๋ฅผ ๋ฐฉ์งํ ์ ์๋ค.
์์ ๋งํ๊ฒ์ฒ๋ผ ์ง์ ์๋ต ํค๋์ origin์ ์ ๋ ๋ฐฉ๋ฒ๋ ๊ฐ๋ฅํ์ง๋ง, CORS ๋ฏธ๋ค์จ์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ค์นํ๊ฒ ๋๋ฉด ๊ฐํธํ๊ฒ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋ค.
`yarn add cors`
์ดํ์, CORS๋ฅผ import ํ๊ณ ์ ์ฉ ์์ผ์ฃผ๋ฉด ๋ฌธ์ ๊ฐ ํด๊ฒฐ๋๋ค.
๋ํ, app.use(cors())
์ ๊ฐ์ด ์
๋ ฅํ๋ฉด origin์์ ๋ค์ด์ค๋ ๋ชจ๋ ์์ฒญ์ ํ์ฉํ๋ ๊ฒ์ด๋ฉฐ,
์ํ๋ origin๋ง ํ์ฉํ๊ณ ์ถ๋ค๋ฉด app.use(cors{origin: })
๊ณผ ๊ฐ์ด ์์ฑํ์ฌ ํน์ ์ค๋ฆฌ์ง๋ง ํ์ฉํ ์ ์๋ค.
// index.js // ...์๋ต import cors from 'cors'; const app = express(); app.use(express.json()); app.use(cors()); // ์ถ๊ฐ๋ ๋ถ๋ถ // ...์๋ต
์ด์ ๋ค์ ๋ฐฑ์๋ ์๋ฒ๋ฅผ ๋ช
๋ น์ด๋ก ์คํด์์ผ๋ณด๋ฉด, ์๋์ ๊ฐ์ด CORS์๋ฌ๊ฐ ๋ํ๋์ง ์๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
์ง๊ธ๊น์ง๋ Rest-api๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด express๋ฅผ ์ฌ์ฉํ์ฌ ์๋ฒ๋ฅผ ์ด์์ง๋ง, ์ง๊ธ๋ถํฐ๋ graphql-api
๋ฅผ ์ฌ์ฉํด๋ณด๊ธฐ ์ํด express์ ์ ์ฌํ ์ญํ ์ ํ๋ apollo-server
๋ฅผ ์ค์นํ๋ ์ค์ต์ ํด๋ณด์!!
$ yarn add @apollo/server graphql
์์ ๋ช
๋ น์ด๋ฅผ ํฐ๋ฏธ๋์์ ์ค์นํ๊ฒ ๋๋ฉด npm ์ฌ์ดํธ์ ์ด๊ธฐ ์์ค์ฝ๋๋ฅผ ๋ด๊ฐ ์์ฑํ ํ์ผ์ ๋ถ์ฌ๋ฃ๊ธฐ ํ๋ค.
์ถ๊ฐ์ ์ผ๋ก, ๋์ด์ฐ๊ธฐ๋ก ๋ชจ๋์ ์ด๋ฆ์ ๊ตฌ๋ถํด์ฃผ๋ฉด ๋ค์์ ๋ชจ๋์ ํ๋ฒ์ ์ค์นํ ์ ์๋ค!!!
์ฝ๋๋ฅผ ์ดํด๋ณด๊ฒ ๋๋ฉด, ์ฐ์ ์ด์ ์ ํ์ตํ๋ express์์ ํน์ ํฌํธ๋ฅผ ํตํด ์๋ฒ๋ฅผ ์ด ์ ์๋๋ก ๋์์ฃผ์๋,
listen
์ด ์๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
๋ํ// index.js import { ApolloServer, gql } from "apollo-server"; const typeDefs = gql` type Query { qqq: String } `; const resolvers = { Query: { qqq: () => { return "Hello World!"; }, }, }; const app = new ApolloServer({ typeDefs: typeDefs, resolvers: resolvers, }); app.listen(3000).then(() => { console.log("๋ฐฑ์๋ API ์๋ฒ๊ฐ ์ผ์ก์ด์!!!"); });
import๋ฅผ ํ๊ธฐ ์ํด์๋ package.json์ "type": "module"
์ ์ถ๊ฐํด์ผ ํ๋ค!!!
{ "name": "03-06-grqphql-api-with-apollo-server", "version": "1.0.0", "main": "index.js", "license": "MIT", "type": "module", // ์ถ๊ฐ "dependencies": { "@apollo/server": "^4.5.0", "graphql": "^16.3.0" } }
์ฝ๋๋ฅผ ์กฐ๊ธ ๋ ์๋ก ์ฌ๋ ค๋ณด๊ฒ ๋๋ฉด,
app
์ ์ ์ํ๊ณ ์๊ณtypedefs
,resolvers
๊ฐ ์ ๋ ฅ๋๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.const app = new ApolloServer({ typeDefs: typeDefs, resolvers: resolvers, });
๋จผ์ , resolver๋ฅผ ๋ณด๊ฒ ๋๋ฉด, express ์๋ฒ๋ฅผ ์ฌ์ฉํ ๋, ๋ณด์๋ api์ ๊ฐ์ ์ญํ ์ ํ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
// express app.get('/', function (req, res) { res.send('Hello Wordld') }) // apollo-server const resolvers = { Query: { qqq: () => 'Hello world', }, };
typeDefs๋ express์์ ๋ณด์ง ๋ชปํ๋ ์ฝ๋์ธ๊ฒ ๊ฐ๋ค.
express์ ๊ฒฝ์ฐ, swagger๋ฅผ ํตํด api-docs๋ฅผ ์ง์ ์์ฑํ์ง๋ง graphql์ ๊ฒฝ์ฐ, docs ๋ถ๋ถ์ typeDefs์์ swagger์ ๊ฐ์ด ์๋์ผ๋ก ์์ฑํด์ค๋ค.
๋ํ CORS์ ๊ฒฝ์ฐ, ApoolloServer ๋ด์ ์ ์ฉ์ํค๋ฉด ๋๋ค.
const app = new ApolloServer({ typeDefs: typeDefs, resolvers: resolvers, cors: true, // ๋ชจ๋ ์ฌ์ดํธ ํ์ฉํ๊ณ ์ถ์ ๋ // cors: { origin: ["https://naver.com", "https://daum.net"] } // ํน์ ์ฌ์ดํธ๋ง ์ง์ ํ๊ณ ์ถ์ ๋ });
http://localhost:3000/graphql
๋ฅผ ์
๋ ฅํ๊ฒ ๋๋ฉด, grapgql์ ์ ์ํ ์ ์๋ค.
Query
: ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๋ฐ์ดํฐ๋ฅผ ์ฝ๋ ์์ฒญ์ด๋ค. (์กฐํ)
Mutation
: ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๋ฑ๋ก, ์์ , ์ญ์ ํ๋ ์์ฒญ์ด๋ค.
์ด์ graphql-api๋ฅผ ํ์ฉํ์ฌ api๋ฅผ ๋ง๋ค์ด๋ณผ ๊ฒ์ด๋ค.
์ด์ ์ ๋ง๋ค์๋ fetchBoards ์ createBoard api๋ฅผ ๋ง๋ค์ด ๋ณด์!!
๊ทธ ์ ์, resolvers ๋ถ๋ถ์ express์ api์ ๊ฐ๊ณ , typeDefs๋ api-docs๋ฅผ ๊ตฌ์ฑํ๊ณ ์๋ต์ ๋๋ ค์ค ํ์
์ ์ ์ํด์ฃผ๋ ๋ถ๋ถ์ด๋ค.
GET
๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ ์ ์๋ค.
// index.js const resolvers = { Query: { fetchBoards: () => { // 1. ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๋ ๋ก์ง => DB์ ์ ์ํด์ ๋ฐ์ดํฐ ๊บผ๋ด์ค๊ธฐ const result = [ { number: 1, writer: '์ฒ ์', title: '์ ๋ชฉ์ ๋๋ค~~', contents: '๋ด์ฉ์ด์์@@@', }, { number: 2, writer: '์ํฌ', title: '์ํฌ ์ ๋ชฉ์ ๋๋ค~~', contents: '์ํฌ ๋ด์ฉ์ด์์@@@', }, { number: 3, writer: 'ํ์ด', title: 'ํ์ด ์ ๋ชฉ์ ๋๋ค~~', contents: 'ํ์ด ๋ด์ฉ์ด์์@@@', }, ]; // 2. ๊บผ๋ด์จ ๊ฒฐ๊ณผ ์๋ต ์ฃผ๊ธฐ return result; }, }, }
rest-api ์์๋ res.send
๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ๋ฅผ ๋ฐํํ์ง๋ง, graphql-api๋ return์ ์ฌ์ฉํ์ฌ ํจ์๋ฅผ ์ข
๋ฃํ๋ฉด์ ๋ฐ์ดํฐ๋ฅผ ๋ฐํํ๋ค.
๋ํ, resolvers์์ ๋ฐํ๋๋ ๊ฐ๋ค์ type ์ญ์ typeDefs์์ ์ง์ ํด์ฃผ์ด์ผ ํ๋ค.
# index.js const typeDefs = gql` type BoardReturn { number: Int writer: String title: String contents: String } type Query { # fetchBoards: BoardReturn => ๊ฐ์ฒด 1๊ฐ๋ฅผ ์๋ฏธ fetchBoards: [BoardReturn] # => ๋ฐฐ์ด ์์ ๊ฐ์ฒด 1๊ฐ ์ด์์ ์๋ฏธ } `;
๋ฐํ๊ฐ์ ๋ฐฐ์ด ์์ ๊ฐ์ฒด์ ํํ๋ก ์ด๋ฃจ์ด์ ธ ์๋ค.
์ต์ข
์ ์ผ๋ก ์๋์ ๊ฐ์ ๊ฒฐ๊ณผ๋ฅผ ์ป์ ์ ์๋ค.
๊ฒ์๊ธ์ ๋ง๋๋ api๋ฅผ ๋ง๋ค์ด๋ณด์!!
์์ฑ ์ฆ, POSTํ๋ ๋ฉ์๋์ด๊ธฐ ๋๋ฌธ์ MUTATION์ ์ฌ์ฉํ์ฌ ์์ฑํ๋ค.
์ด๋, ์์ฑ๋ ํจ์๋ฅผ ํตํด ์
๋ ฅ๊ฐ์ ๋ฐ์ ์ ์๋๋ฐ, ํด๋น ํจ์๋ 4๊ฐ์ ๋งค๊ฐ๋ณ์๋ฅผ ๊ฐ์ง ์ ์๋ค.
parent
: ๋ถ๋ชจํ์
resolver์์ ๋ฐํ๋ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ง ๊ฐ์ฒดargs
: ์ฟผ๋ฆฌ ์์ฒญ ์ ์ ๋ฌ๋ parameter๋ฅผ ๊ฐ์ง ๊ฐ์ฒดcontext
: graphql์ ๋ชจ๋ resolver๊ฐ ๊ณต์ ํ๋ ๊ฐ์ฒด (๋ก๊ทธ์ธ ์ธ์ฆ, ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ๊ทผ ๊ถํ ๋ฑ์ ์ฌ์ฉ๋๋ค)info
: ๋ช
๋ น ์คํ ์ํ ์ ๋ณด๋ฅผ ๊ฐ์ง ๊ฐ์ฒด // index.js const resolvers = { Mutation: { createBoard: (parent, args, context, info) => { }, }, };
rest-api์์๋ ์์ฒญ ๋ฐ์ดํฐ๋ฅผ ํ์ธํ๊ธฐ ์ํด ๋งค๊ฐ๋ณ์๋ก req๋ฅผ ์ฌ์ฉํ๋ค.
graphql์์๋ 4๊ฐ์ ๋งค๊ฐ๋ณ์ ์ค ์์ฒญ ๋ฐ์ดํฐ๋ฅผ ํ์ธ ๊ฐ๋ฅํ args๋ฅผ ์ฌ์ฉํ์ฌ ์
๋ ฅ๊ฐ์ ๊ฐ์ ธ์จ๋ค.
// index.js const resolvers = { Query: { fetchBoards: () => { // ... }, }, Mutation: { createBoard: (_, args) => { // 1. ๋ธ๋ผ์ฐ์ ์์ ๋ณด๋ด์ค ๋ฐ์ดํฐ ํ์ธํ๊ธฐ console.log(args); console.log("=========================") console.log(args.createBoardInput.writer) console.log(args.createBoardInput.title) console.log(args.createBoardInput.contents) // 2. DB์ ์ ์ ํ, ๋ฐ์ดํฐ๋ฅผ ์ ์ฅ => ๋ฐ์ดํฐ ์ ์ฅํ๋ค๊ณ ๊ฐ์ // 3. DB์ ์ ์ฅ๋ ๊ฒฐ๊ณผ๋ฅผ ๋ธ๋ผ์ฐ์ ์ ์๋ต(response) ์ฃผ๊ธฐ return '๊ฒ์๋ฌผ ๋ฑ๋ก์ ์ฑ๊ณตํ์์ต๋๋ค!!'; }, }, };
์ด๋ฒ์๋ ์์ฒญ ๋ฐ์ดํฐ ํ์
์ ์ง์ ํด๋ณด์๋ค.
์๋์ ๊ฐ์ด input
์ ์ฌ์ฉํด ๋ฐ์ดํฐ์ ํ์
์ ์ง์ ํ๋ค.
# index.js const typeDefs = gql` input CreateBoardInput { writer: String title: String contents: String } type Mutation { # createBoard(writer: String, title: String, contents: String): String => ์ ๋ ฅ๊ฐ์ ๋ฑ๊ฐ๋ก ๋ฐ์์ค๋ ๊ฒ์ ์๋ฏธ createBoard(createBoardInput: CreateBoardInput!): String # => ์ ๋ ฅ๊ฐ์ ๊ฐ์ฒด๋ก ๋ฐ์์ค๋ ๊ฒ์ ์๋ฏธ } `;
์ต์ข
์ ์ผ๋ก apollo sever๋ฅผ ์ด๊ณ localhost:3000/graphql
์ ์ ์ํด์ api ํ
์คํธ๋ฅผ ์งํํด๋ณธ๋ค.
# index.js mutation { createBoard(createBoardInput: { writer: "์ฝ๋์บ ํ", title: "์จ๋ผ์ธ ๋ถํธ์บ ํ ๋ฐฑ์๋", contents: "๋งค์ฐ ์ข์ต๋๋ค!!!" }) }
์ ์ ์กฐ๊ธ์ฉ ์ง์ณ๊ฐ๋ ์์ฆ์ด๋ค. ๋ ์จ๋ ๋ง์ด ํ๋ ค ๋ด์ด ์ค๊ณ ์๊ณ , ๋๋ฅธํด์ง๋ ๊ธฐ๋ถ์ด ๋ ๋ค. ํญ์ ๊ฒฐ์ฌ์ ์ ์งํ๋ ๊ฒ์ด ์ด๋ ค์ด ๊ฒ ๊ฐ๋ค. ๋ฌผ๋ก ์ค๋๋ ์ต์ ์ ๋คํ ํ๋ฃจ์์ง๋ง, ์กฐ๊ธ์ฉ ์ง์น๋ ๊ฒ์ด ๋๊ปด์ ธ ๊ฑฑ์ ์ด๋ ๋ค. ๋ฌธ์ ๋ ํ๋ฃจํ๋ฃจ ์์ฌ๊ฐ๋ ๋ณต์ต๋์ด๋ค... ๊ทธ๋๊ณ ๊ธ์ ์ ์ธ ๋ง์์ผ๋ก ํ๋ฃจํ๋ฃจ ์ต์ ์ ๋คํ ๋ ค ๋ ธ๋ ฅ ํ ๊ฒ์ด๋ค!!!!