[NODE.JS(노드)] bitly/tinyurl같은 URL 단축 사이트 만들기
프로젝트/Node.JS

[NODE.JS(노드)] bitly/tinyurl같은 URL 단축 사이트 만들기

계속 공부만 하다가 프로젝트를 하고 싶어서 간단히 URL 단축 사이트를 만들어 봤습니다.

기술 스택:

Node.JS, MongoDB, HTML, CSS, Bootstrap

NPM 모듈:

Express, BodyParser, EJS, Mongoose, Nanoid, dotenv

(이 포스트에서는 자세하게 어떤 코드가 어떤 일을 하는지 설명하지 않습니다.)

완성된 결과는 여기에서 확인하세요!


***12/2월 업데이트 - 비밀번호 도입/복사***

***12/8 업데이트 - 복사 기능 업데이트***

사실 처음 생각은 이걸 누구나 Heroku(무료 호스팅 서비스) 같은 곳에 쉽게 호스팅 하게 하고 싶었는데

하다 보니까 (저한테는) 간단한 프로젝트가 아니게 돼서 그냥 제 개인용으로 만들었습니다.

작동원리는 그냥 간단하게 만들었습니다.

몽고디비 아틀라스랑 연결해서 클라우드로 데이터베이스를 연결하고 nanoid 모듈을 통해 단축 링크를 생성합니다.

몽고 디비 스키마는 단축 링크, 기존 링크, 클릭 횟수 세 가지로 구성되어 있습니다.

(사실 클릭 횟수는 필요 없는데 혹시라도 코드 쓰실 분 있으면 사용하세요)

// 몽고디비 스키마
const linkSchema = new mongoose.Schema({
  identifier: {
    required: true,
    type: String,
  },
  url: {
    required: true,
    type: String,
  },
  clicks: {
    required: true,
    type: Number,
    default: 0,
  }
});

만약 새로운 단축 링크를 생성해야하면 nanoid를 통해 (알파벳, 숫자, -, _)들로 구성된 랜덤 한 7 문자 문자열이 생성됩니다.

만약 이 문자열이 데이터베이스에 등록되어있지 않으면 이 문자열, 기존 링크, 클릭 횟수(0)로 새로운 document를 디비에 추가합니다. 

그리고 링크가 http(s):// 로 시작하지 않으면 이후 해당 링크로 이동이 안되기 때문에 http://를 추가해줍니다.

(s가 없으면 보안이 떨어지는게 아니냐고 할 수 있는데 그건 해당 웹사이트에서 자동으로 맞게 변형합니다.)

그리고 비밀번호가 틀리면 틀렸다는 말이 뜨는 HTML을 렌더링 합니다.

(예쁘게 만들 수는 있는데 사실 사이트 주인만 볼 화면이라 용량도 아낄 겸 HTML만 보내게 했습니다.)

app.post('/shorten', async(req, res) => {
    if (req.body.PW == process.env.PW){
        let id = null
        while (1) {
            id = nanoid(7)
            if (await Link.exists({ identifier: id })) continue;
            else break;
        }
    
        let url = req.body.URL
        if (!(url.startsWith('https://') || url.startsWith('http://'))) {
            url = 'http://' + url
        }
    
        await Link.create({
            identifier: id,
            url: url,
            clicks: 0
        })
    
        res.redirect('/')
    }else{
        res.send(`<div style="display: flex; justify-content: center;">
        <h1>Wrong Password</h1>
      </div> `)
    }
});

HTML 파일은 간단하게 줄일 링크를 넣을 form이랑 현재 디비에 있는 것들을 간단히 관리할 수 있는 테이블을 넣었습니다.

또한 초록 버튼을 클릭하면 링크를 복사해서 클립보드에 넣습니다.

function copy(id){
      const el = document.createElement('textarea')
      el.value = <link> + id
      el.setAttribute('readonly', '')
      el.style.position = 'absolute'
      el.style.left = '-9999px'
      document.body.appendChild(el)
      el.select()
      document.execCommand('copy')
      document.body.removeChild(el)
      alert('Link Copied!')
    }

 

빨간 버튼은 해당 document를 지우는 버튼으로 아래와 같이 구현되었습니다.

이것 또한 비밀번호를 입력해야 하며 구성은 위의 shorten과 같습니다.

app.post('/delete', (req, res) => {
    if (req.body.PW == process.env.PW) {
        Link.findOneAndRemove({ identifier: req.body.ID }, (err, deleted) => {
            if (err) {
                console.log(err)
            } else {
                console.log(deleted)
            }
        })
        res.redirect('/')
    }else{
        res.send(`<div style="display: flex; justify-content: center;">
        <h1>Wrong Password</h1>
      </div> `)
    }
})

마지막으로 이 프로젝트의 핵심인 다른 링크로 이동하는 코드입니다.

저기서 ':url'은 nanoid로 생성된 단축 링크로
이 단축 링크를 통해 데이터베이스에서 기존 링크를 얻어 그 링크로 이동합니다. 

+비밀번호 기능을 넣다가 제가 알던 문법이랑 API 문법이 바뀐 거 같아 수정했습니다.

이제 링크가 없다고 HTML을 렌더링 합니다.

app.get('/:url', (req, res) => {
    Link.findOne({ identifier: req.params.url }).then( (link) => {
        if (link === null) {
            res.send(`<div style="display: flex; justify-content: center;">
            <h1>Link Not Found</h1>
          </div> `)
        }else{
            link.clicks++;
            link.save()

            res.redirect(link.url)
        }
    })
})

 

***수정 사항***

freenom에서 무료 도메인을 받아서 연결을 해보니 문제점 몇 가지를 발견했다.

1. SSL(보안) 인증이 안 된다.(개인 목적으로 비밀번호만 다른 곳에서 쓰지 않은 것을 쓰면 큰 문제는 없다)

2. 도메인 앞에 www를 꼭 붙여야 한다.(freenom에서는 www 없이 등록이 안된다.)

결론:
CSS로 포지셔닝하는 게 오래 걸렸지 코드 자체는 간단히 구현할 수 있는 백엔드 프로젝트였다.

또한, 이번에 freenom을 사용하면서 이 프로젝트에 돈을 써가며까지 할 건 없었기에 사용했지만 집중적으로 관리할 사이트는 꼭 도메인을 구매를 해야겠다.

그래도 대부분의 기능을 하루 만에 구현한 것 치고는 깔끔하게 구현한 것 같다.