[firebase실강] 3강

youngseo·2022년 7월 5일
0

FireBase

목록 보기
3/9
post-thumbnail

3강

1. 이해하기

우리의 코드는 현재 백엔드에서 돌아가고 있습니다.

  • put을 날리면 request로 처리후 반환되는 그 정보를 받습니다.
  • 웹서버 입장에서는 대상을 찾아서 수정을 해야하는데 그 대상이 DB에 들어있습니다.
  • 따라서 DB컬렉션을 통해서 통신을 왔다갔다합니다
  • 그리고 수정완료된 데이터를 최종적으로 클라이언트에 넘겨줍니다.
  • 클라이언트와 웹서버가 통신하는 것도 네트워크, 웹서버와 DB가 통신하는 것도 네트워크입니다.
    • 보통 웹서버가 DB는 원격으로 떨어져 있는 경우도 있지만 파이어베이스는 구글클라우드플랫폼으로 묶여있기 때문에 비교적 웹서버가 DB가 통신하는 것이 조금 더 빠릅니다.
    • 단 클라이언트와 웹서버가 통신하는 것은 네트워크비용이 비싸(돈, 시간비용, 처리비용 등)기 때문에 최소한의 통신이 필요합니다.

2. 실습전 세팅

axios 설치

루트경로에 axios를 설치해줍니다.

$ npm i axios

emuls실행

$ npm run fb:emuls
$ functions > npm run build:watch //ts => js

3. (PUT)Todo수정

//투두수정
router.put('/:id', async(req, res) => {
  const {title, done} = req.body 
  const {id} = req.params

  const snap = await db.collection('Todos').doc(id).get()
  const {createdAt} = snap.data() as Todo
  const updatedAt = new Date().toDateString

  await snap.ref.update({ //이것은 db를 갱신해주는 것이지 snap을 갱신해주는 것은 아닙니다.
    title,
    done,
    updatedAt: new Date().toDateString()
  })

  res.status(200).json({
    id: snap.id,
    title,
    done,
    createdAt,
    updatedAt
  })
})
  1. 요청부분(req.body)에서 title, done을 꺼낼 수 있습니다.
  2. 동적 파라미터 설정
  • https://asia-northeast3-heropy-api.cloudfunctions.net/api/todos/:todoId와 같이 주소 부분에 파라미터로 받아서 어떤 id를 가지는 항목을 수정할 것인지를 설정해줘야합니다.
  • 위와 같이 REST API형태로 하기도 하지만 body부분에 담아서 요청하기도 합니다.(즉, API설계에 따라서 달라집니다.)
  • /:id로 동적파라미터를 설정해줍니다.
  1. id를 req.params에서 뽑아옵니다.
  2. db.collection('Todos').doc(id) doc()메서드를 이용하면 원하는 정보만을 뽑아올 수 있습니다.
  3. get() 메서드로 그렇게 뽑아온 데이터를 가지고 옵니다.
  4. 원하는 아이디만 뽑아온 것이므로 snaps가 아닌 snap으로 받아옵니다.
  • 기다려야하므로 await를 붙여줍니다.
  1. snapref속성에는 update라는 메소드가 있습니다. 이 update메소드를 통해 db에서 찾은 데이터를 업데이트 해줍니다.
  • 기다려야하므로 await를 붙여줍니다
  1. 실제로 사용자는 title와 done정보의 업데이트를 요청한 것이지만 실제로 DB에 업데이트된 시간도 업데이트 될 수 있도록 함께 넣어줍니다.
  2. 이렇게 업데이트한 결과를 다시 사용자에게 응답해줍니다.
  • snap에서 id를 뽑아 반환합니다.
  • 전송했던 title, done을 그대로 전달해줍니다,
  • createdAt의 경우 snap에서 뽑아야합니다.
    • db에 있는 필드를 객체형식으로 뽑아주는 data()메소드를 이용합니다.
  • updatedAt의 경우 요청하는 시간과 응답하는 시간이 다르지 않도록 하나의 변수로 설정해 관리를 합니다.
  • createdAt의 경우 snap.data()에서 가져와 사용을 합니다.
    • const { createdAt } = sanp.data()와 같이 객체구조분해할당을 하는 경우 타입스크립트 에러가 나게 됩니다.
    • data()메소드를 호출에 나오는 결과는 DB의 구조에 따라 다 다를 수 있기 때문입니다. 즉, ts 입장에서는 타입이 정해지지 않은 것입니다. 그렇기 때문에 as Todo를 통해 단언을 해줍니다.

await snap.ref.update는 DB를 갱신해주는 것이지 snap을 갱신해주는 것은 아닙니다. 즉, snap을 그대로 res.status에서 사용해서는 안됩니다.
따라서 최신에서 사용한 그 값을 snap.ref.update에서 사용한 후 res.status에서 갱신을 해주는 것입니다.

snap : DB에서 찾은 도큐먼트를 활용할 수 있는 객체
snap.data() : snap에서 data메소드를 사용하면 그 객체의 필드들이 다 반환됩니다.(ex, fileds.title, fileds.done, fileds.createdAt 등)


정상적으로 동작하는 것을 확인할 수 있습니다.

3. todo 데이터 삭제

3-1 DB에서도 지우기

//todo 삭제
router.delete('/:id', async(req, res) => {
  const { id } = req.params

  const snap = await db.collection('Todos').doc(id).get()
  await snap.ref.delete()

  res.status(200).json(true)
})

export default router
  • 동적파라미터로 들어온 값(id)req.parmas에서 뽑아냅니다.

3-2 DB에는 남겨놓는 DELETE

DB에서 이렇게 데이터를 날려보내면 데이터 추적이 어렵기 때문에 실제로 서버개발자는 실제로 지우지 않고 지워진 것처럼 만들고 30일후에 지워지는 등 과 같이 만듭니다. 이런 경우 delete를 쓰지 않고 추후 스케줄러를 거는 방식으로 합니다.

//todo 삭제
router.delete('/:id', async(req, res) => {
  const {id} = req.params
  const snap = await db.collection('Todos').doc(id).get()
  await snap.ref.update({
    deleted: true //이렇게 있으면 지어진 데이터로 취급
  })

  res.status(200).json(true)
})

export default router

3-3 GET메소드 변경

실제로 데이터를 지우지 않고 delete가 있는 경우 지워진 상태로보기 때문에 조회시 필터를 해줘야합니다,

//todo조회
// https://localhost:5001/kdt-test-9f352/us-central1/api/todo/heropy
router.get('/', async (req, res) => {

  const snaps = await db.collection('Todos')
    .where('deleted', '!=', true)//✅이부분 추가
    .get()

  const todos: Todo[] = []
  snaps.forEach(snap => {
    const fields = snap.data()
    todos.push({
      id: snap.id,
      ...fields as Todo
    })
  })

3-4. 갱신

기존 값을 조회하려면, deleted가 true가 아닌 값이 미리 들어가 있어야 합니다 즉, 이전에 넣은 값들에도 deleted를 설정해줘야합니다.

JOb만들기

  • 있는 데이터들을 수정하기 위해 1회성으로 사용할 예정입니다.
//job
function addDeleted() {
  const snaps = await db.collection('Todos').get()

  snaps.forEach(async snap => { // ⭐불가능합니다.
    await snap.ref.update({
      deleted:false
    })
  })
  console.log('완료')
}

foreach에서 async await를 쓴다고 해서 foreach에서 수행되는 로직을 다 기다리고 console로 넘어가지 않습니다
그래서 필요한 것이 바로 for문입니다

//job
async function addDeleted() {
  const snaps = await db.collection('Todos').get()

  for(const snap of snaps.docs){
    await snap.ref.update({
      deleted: false
    })
  }

  console.log('끝')
}

3-5 POST수정

이제는 deleted라는 속성도 필요하기 때문에 POST 메소드에 추가를 해줍니다.

interface Todo {
  id?: string
  title: string
  done: boolean
  createdAt: string
  updateAt: string,
  deleted: boolean✅
}

//투두 추가
router.post('/', async (req, res) => {
  const {title} = req.body
  const date = new Date().toISOString()
  const todo = {  //4. documnet내용작성
    title,
    done: false,
    //IOS는 국제표준시로 date를 생성합니다.
    createdAt: date,
    updatedAt: date,
    deleted: false}

  const ref = await db.collection('Todos').add(todo) 

  //생성된 데이터를 응답
  res.status(200).json({
    id: ref.id,
    ...todo
  })
})

3-6 addDeleted실행

addDeleted()

addDelted()메소드를 딱 한번 실행을 하고 저장을 해줍니다. 이후 addDelted()는 지워줍니다.

3-7 예외처리

잘못된 id에 대해 조회를 하려는 경우 조회가 되지 않도록 예외처리를 해보겠습니다.

router.delete('/:id', async(req, res) => {
  const {id} = req.params
  const snap = await db.collection('Todos').doc(id).get()

  // 예외처리
  if(!snap.exists) {
    return res.status(404).json('존재하지 않는 정보입니다.')}
  await snap.ref.update({
    deleted: true //이렇게 있으면 지어진 데이터로 취급

  })

  res.status(200).json(true)
      return//ts에러해결
})
  • snapexists속성을 이용하면 내용이 존재하는지 아닌지를 판단할 수 있습니다.

마찬가지로 id가 필요한 투두 수정에서도 예외처리를 해줍니다.

router.put('/:id', async(req, res) => {
  const {title, done} = req.body 
  const {id} = req.params

  const snap = await db.collection('Todos').doc(id).get()
  if(!snap.exists) {
    return res.status(404).json('존재하지 않는 정보입니다.')}

  const { createdAt } = snap.data() as Todo
  const updatedAt = new Date().toDateString()

  await snap.ref.update({
    title,
    done,
    updatedAt
  })

  return res.status(200).json({
    id: snap.id,
    title,
    done,
    createdAt,
    updatedAt
  })

})

4. Todo응답 데이터 정렬

  • 일정한 기준으로 정렬해 반환될 수 있도록 정렬 기준을 잡아줍니다.
//투두 조회
router.get('', async (req, res) => {
  const snaps = await db.collection('Todos')
    .where('deleted', '!=', true)
    .get()
  const todos: Todo[] = []

  snaps.forEach(snap => {
    const fields = snap.data()
    todos.push({
      id: snap.id,
      ...fields as Todo
    })
  })

  todos.sort((a, b) => { //숫자를 기준으로 비교할 수 있도록 new Date().getTime()
    const aTime :number = new Date(a.createdAt).getTime()
    const bTime :number = new Date(b.createdAt).getTime()
    return bTime-aTime
  })

  res.status(200).json(todos)
})

firebase.json파일의 emulators에서 어떤 것이 사용되고 있는지 확인할 수 있습니다.

...
  "emulators": {
    "auth": {
      "port": 9099 // FIREBASE에서 제공하는 회원가입, 로그인 관련: 헤로피님 블로그 추천
    },
    "functions": {
      "port": 5001 //넷니파이나 버셀에서 사용하는 서버리스 함수와 같은 개념(서버를 구축하지 않아도 돌릴 수 있는 서버코드
    },
    "firestore": { //데이터베이스(일반)
      "port": 8080
    },
    "database": { //리얼타임데이터베이스(기능이 도입되기 전, 실시간 통신하면서 서버에서의 정보가 변하면 프론트엔드의 정보도 변함.)
      "port": 9000
    }, 
    "hosting": { //싱글페이지 어플리케이션, 백엔드가 동작하지 않아도 서버를 업로드할 수 있는 서비스
      "port": 5000
    },
    "pubsub": { //스케줄러(코드를 실행하는 스케줄: 스크랩핑, 크롤링)나 메세지 전송할 때 쓰는 기능
      "port": 8085
    },
    "storage": { // 이미지 파일 저장
      "port": 9199
    },
    "ui": {
      "enabled": true 
    }
  },
  "remoteconfig": {
    "template": "remoteconfig.template.json"
  }
}

0개의 댓글