학습 기록/데브코스 웹 풀스택 4기

백엔드 기초: 전체 조회, forEach와 map, delete

romi__ 2024. 9. 5. 14:35

date/ 24.09.05.

 /* 공부하며 정리한 내용이니 정확하지 않을 수 있습니다. 잘못된 내용은 댓글로 알려주세요!*/

 

 

📌

전체 유튜버가 조회되는 화면을 만들다 말았습니다. 마저 제작해 보겠습니다.

app.get('/youtubers', (req, res) => {
	db.forEach(function(youtuber) {
    	console.log(youtuber)
    })
    res.json(db.values())
})

 

이런 코드를 일단 추가해 주었습니다. 잘 실행되는지 로그를 통해 확인해 보겠습니다.

 

잘 뜨는군요! 그럼 다음 단계로 넘어가서 ...

 

app.get('/youtubers', (req, res) => {
	var youtubers = {}
    db.forEach(function(value, key) {
    	youtubers[key] = value
    })
    res.json(youtubers)
})

 

이렇게 작성해 보았습니다.

 

여기서 의문이 생깁니다. 대체 forEach가 뭔데? 그게 Map이랑 뭐가 다른 건데?

 

 

📌

forEach는 이름에서도 알 수 있듯 for + each로, 누군가는 향상된/개선된 for문이라고도 부릅니다. 배열로 사용 예시를 들어보겠습니다.

const arr = [1,2,3,4,5]

arr.forEach((a) => {
	console.log(a)
})

 

이러한 간단한 코드를 실행시켜 보았습니다.

 

 

이렇게 로그가 찍힙니다. a가 data값이 되었군요. 그럼 매개변수를 임의의 이름(b)으로 하나 더 추가하면 어떻게 될까요?

 

const arr = [1,2,3,4,5]

arr.forEach((a, b) => {
	console.log(`a : ${a}, b : ${b}`)
})

 

이런 코드를 작성하고 실행해 보았습니다.

 

 

보시다시피 a는 값, b는 인덱스를 전달하고 있습니다. 즉, 변수 이름은 전혀 상관이 없고 정해진 순서대로 전달을 해 주는 것입니다. 하나 더 해봅시다.

 

const arr = [1,2,3,4,5]

arr.forEach((a, b, c) => {
    console.log(`a : ${a}, b : ${b}, c : ${c}`)
})

 

이런 코드를 실행하면

 

 

데이터값, 인덱스값에 이어서 배열 전체가 출력됩니다. 다시 말해, forEach문은 요소에 대해 한 번씩 함수를 실행하며, 해당 요소(매개변수)는 순서에 따라 데이터값, 인덱스값, 배열 전체가 해당합니다. 그럼 배열에선 확인했으니, map은 어떻게 활용되는지 확인해 보겠습니다.

 

let map = new Map()
map.set(7, "seven")
map.set(9, "nine")
map.set(8, "eight")

map.forEach((a) => {
	console.log(a)
})

 

의도적으로 순서를 섞어 주었습니다. 이 코드를 실행해 보겠습니다.

 

 

로그가 잘 찍힙니다. 맵 역시 set을 해둔 순서대로 찍히는 것을 볼 수 있습니다. 배열과 상당히 유사하게 작동하고 있는 모습을 볼 수 있습니다. 중요한 것은 여기서도 마찬가지로 매개변수 이름이 아니라 순서대로 가져오는 값이 정해져 있다는 것입니다.

 

 

📌

그렇다면 map과 forEach를 비교하며 살펴보도록 하겠습니다.

//map함수(메서드) vs. forEach

const arr = [1,2,3,4,5]

arr.forEach((a, b, c) => {
    console.log(`a : ${a}, b : ${b}, c : ${c}`)
})

arr.map((a, b, c) => {
    console.log(`a : ${a}, b : ${b}, c : ${c}`)
})

 

이렇게 코드를 작성해 주고 실행을 시켜주면

 

 

결과는 동일합니다. 그럼 뭐가 다를까요? return에 그 차이가 존재합니다.

 

//map함수(메서드) vs. forEach

const arr = [1,2,3,4,5]

const foreachArr = 
    arr.forEach((a, b, c) => {
    return a * 2
})

const mapArr = 
    arr.map((a, b, c) => {
    return a * 2
})

console.log(`forEach로 return하면 ${foreachArr},
            map으로 return하면 ${mapArr}`)

 

이렇게 코드를 작성하고 실행시켰더니, 엥? 결과가 좀 이상합니다.

 

 

forEach의 return값은 undefined로 표기되는 반면, Map은 제대로 출력되네요. 무슨 차이일까요?

 

forEach는 return을 하는 척만 하고 저장하진 않습니다. 목적과 동작 방식에 차이가 있어서 나타나는 현상인데, forEach는 배열의 각 요소에 대해 제공된 함수를 실행하는 것이 목적입니다. 반환값을 무시하고 단순히 작업을 수행하는 것입니다. 그래서 저장하거나 처리하는 것이 없는 것이죠. 즉, 함수 내에서 return을 하더라도 forEach 자체가 그 값을 모아서 새로운 배열을 만들지 않습니다. 따라서 위의 코드처럼 작성할 경우 foreachArr은 항상 undefined가 됩니다.

 

반면 map의 경우 배열의 각 요소에 대해 제공된 함수를 실행하고, 그 결괏값을 모아서 새로운 배열을 반환합니다. 즉, 각 Return값이 저장되어 새로운 배열을 만드는 데 사용되는 것입니다. 

 

 

 

📌

전체 유튜버를 조회하는 데 성공했으니 이제 개별 유튜버를 삭제해 보겠습니다. method는 당연히 delete를 사용하고, url은 /youtubers/:id로 받아주도록 하겠습니다. 개별 유튜버 조회와 URL이 동일하지만 괜찮습니다. method가 달라 서로 구분되기 때문입니다. request는 params.id(개별 유튜버를 특정해서 삭제해야 하므로), resonse는 "channelTitle님, 다음에 다시 만나요!"와 같은 메시지를 보내도록 하겠습니다.

 

app.delete('/youtubers/:id', (req, res) => {
	let id = req.params.id
    id = parseInt(id)
    
    const name = db.get(id).channelTitle //삭제 전 미리 받아주기 -> json에서 활용
    db.delete(id)
    
    res.json({
    	message : `${name}님, 다음에 다시 만나요!`
    })
})

 

이렇게 작성해 주면 되겠죠? postman을 통해 실행해 보겠습니다.

 

잘 작동하는군요. 그럼 이제 기존에 할당되지 않았던 번호를 Id로 적어서 지우겠다고 보내면 발생할 수 있는 에러를 처리해 보겠습니다. 먼저 존재하는 id인지를 확인한 후에, 존재하지 않는다면 에러 대신 메시지를 보낼 수 있도록 하고, 존재하는 Id라면 기존 코드대로 정보를 삭제하도록 If문을 작성하겠습니다.

 

app.delete('/youtubers/:id', (req, res) => {
    let id = req.params.id
    id = parseInt(id)

    var youtuber = db.get(id)
    if (youtuber == undefined) {
        res.json({
            message : `요청하신 ${id}번에 할당된 유튜버가 없습니다. 다시 시도하세요.`
        })
    } else {
        const name = youtuber.channelTitle
        db.delete(id)
        res.json({
        message : `${name}님, 다음에 다시 만나요!`
        })
    }
})

 

이렇게 코드를 작성한 후, 먼저 없는 id를 삭제하겠다고 요청을 보내봅니다.

 

제대로 작동하는군요. 그럼 있는 번호는 어떨까요?

 

야호! 의도한 대로 잘 작동합니다.

 

 

📌

그럼 내가 유튜버의 신, 유튜브의 관리자라고 생각하고 깔짝깔짝 개별 유튜버를 삭제하는 것이 아니라 전체 유튜버 리스트를 삭제할 수 있는 URL을 설정해 보겠습니다. 당연히 method는 delete를 사용할 테고, URL은 /youtubers로 연결해 보겠습니다. Request는 없고, response로 "전체 유튜버 목록이 삭제되었습니다"라는 메시지를 보내주겠습니다.

 

app.delete('/youtubers', (req, res) => {
    db.clear()
    res.json({
        message : `전체 유튜버 목록이 삭제되었습니다.`
    })
})

 

이렇게 코드를 작성하고, 먼저 delete로 send해 주겠습니다.

 

제대로 삭제가 되었는지 GET에서도 확인해 보겠습니다.

 

텅~ 비었군요. 성공입니다.

 

그런데 만약 전체 삭제를 할 유튜버가 없다면 어떻게 될까요? 전체 삭제를 1회 send한 다음 또 한 번 send를 눌러도 똑같은 메세지, "전체 유튜버 목록이 삭제되었습니다."가 출력됩니다. 에러는 나지 않아서 예외 처리할 내용은 아니지만, 오해의 소지가 있으니 고려하여 해결하는 것이 좋겠습니다. DB에 있는 값이 1개 이상이면 전체 삭제가 가능하도록 하고, 값이 존재하지 않으면 "삭제할 유튜버가 존재하지 않습니다."라는 메세지를 출력하도록 설정해 주겠습니다.

 

app.delete('/youtubers', (req, res) => {
    var msg = ""
    if (db.size >= 1) {
        db.clear()
        msg = `전체 유튜버 목록이 삭제되었습니다.`
    } else {
        msg = `삭제할 유튜버가 존재하지 않습니다.`
    }

    res.json({
        message : msg
    })
})

 

다시 실행하여서 delete send를 두 번 보내보겠습니다.

 

의도한 대로 메세지가 출력됩니다.

 

 

📌

마지막으로 method 중 put을 활용해 보겠습니다. 개별 유튜버 수정을 위해 사용할 것이고, URL은 /youtubers/:id로 받아보겠습니다. 어떤 데이터를 수정하겠다고 받아야 할까요? 사실 유튜버 정보 중 수정할 내용은 딱 한 가지입니다. channelTitle이겠죠. subscriber(구독자)나 videos(영상 수)는 사용자가 수정하고 싶다고 해서 수정할 수 있도록 내버려 둬서는 안 되는 부분입니다. 때문에 youtuber 객체 중 channelTitle이라는 property만 받아와 수정해 보도록 하겠습니다.

 

Request는 params.id 뿐 아니라 body에 channelTitle을 받아 와야 하고, response로는 "(이전 채널명)님, 채널 이름이 (신규 채널명)(으)로 변경되었습니다."라는 메세지를 출력하도록 설정하겠습니다.

 

app.put('/youtubers/:id', (req, res) => {
    let id = req.params.id
    id = parseInt(id)

    var youtuber = db.get(id)
    var oldTitle = youtuber.channelTitle
    if (youtuber == undefined) {
        res.json({
            message : `요청하신 ${id}번에 할당된 유튜버가 없습니다. 다시 시도하세요.`
        })
    } else {
        var changedTitle = req.body.channelTitle

        youtuber.channelTitle = changedTitle
        db.set(id, youtuber)

        res.json({
            message : `${oldTitle}님, 채널 이름이 ${changedTitle}(으)로 변경되었습니다.`
        })   
    }
})

 

채널명이 수정된 이후에 이전 채널명을 받아올 순 없으므로 수정 전에 미리 oldTitle이라는 변수 안에 이전 채널명을 넣어 주었습니다. 이렇게 코드를 작성하고 postman에서 실행해 보았습니다.

메세지가 제대로 출력되는군요. GET을 통해 전체 객체를 불러와 제대로 적용되었는지도 확인해 보겠습니다.

 

아주 잘 들어갔습니다. 뿌듯하네요.

 

 

/*사족입니다*/

  • 오... 백엔드를 시작한 이후로 처음(!) 흥미를 느끼는 파트를 만났다. 내가 지정한 대로 코드를 실행하면 실제도 db에도 영향을 끼친다는 게 재밌는 부분... 그래도 아직은 눈에 보이는 게 더 좋다.
  • 프로그램 자체에 많은 것을 기대하지 말자. 오만하지 말고 성실하게 잘하자... 환경이 얼마나 구린지는 상관없이...^^