
우리의 코드는 현재 백엔드에서 돌아가고 있습니다.
put을 날리면 request로 처리후 반환되는 그 정보를 받습니다.루트경로에 axios를 설치해줍니다.
$ npm i axios$ npm run fb:emuls
$ functions > npm run build:watch //ts => js//투두수정
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
  })
})req.body)에서 title, done을 꺼낼 수 있습니다.https://asia-northeast3-heropy-api.cloudfunctions.net/api/todos/:todoId와 같이 주소 부분에 파라미터로 받아서 어떤 id를 가지는 항목을 수정할 것인지를 설정해줘야합니다.REST API형태로 하기도 하지만 body부분에 담아서 요청하기도 합니다.(즉, API설계에 따라서 달라집니다.)/:id로 동적파라미터를 설정해줍니다.db.collection('Todos').doc(id) doc()메서드를 이용하면 원하는 정보만을 뽑아올 수 있습니다.get() 메서드로 그렇게 뽑아온 데이터를 가지고 옵니다.snap으로 받아옵니다. snap의 ref속성에는 update라는 메소드가 있습니다. 이 update메소드를 통해 db에서 찾은 데이터를 업데이트 해줍니다.snap에서 id를 뽑아 반환합니다.data()메소드를 이용합니다.snap.data()에서 가져와 사용을 합니다.const { createdAt } = sanp.data()와 같이 객체구조분해할당을 하는 경우 타입스크립트 에러가 나게 됩니다.
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 등)

정상적으로 동작하는 것을 확인할 수 있습니다.
//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 routerid)를 req.parmas에서 뽑아냅니다.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실제로 데이터를 지우지 않고 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
    })
  })기존 값을 조회하려면, deleted가 true가 아닌 값이 미리 들어가 있어야 합니다 즉, 이전에 넣은 값들에도 deleted를 설정해줘야합니다.
//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('끝')
}이제는 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
  })
})
addDeleted()addDelted()메소드를 딱 한번 실행을 하고 저장을 해줍니다. 이후 addDelted()는 지워줍니다.
잘못된 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에러해결
})snap의 exists속성을 이용하면 내용이 존재하는지 아닌지를 판단할 수 있습니다.마찬가지로 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
  })
})
//투두 조회
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"
  }
}