Never give up

React, Node - Rest API with sqlite3 (beginner) - 1 본문

WEB

React, Node - Rest API with sqlite3 (beginner) - 1

대기만성 개발자 2022. 1. 8. 00:16
반응형

이전에 만들었던것을 조금 더 개선시켜서 만들어봤습니다

(링크 : https://devmemory.tistory.com/77)

 

node는 sqlite3를 연결, 서버부분 에러처리 추가

react는 자잘한 에러 수정 및 pagination 추가

그리고 proxy추가 및 변경된 부분 양쪽 다 처리

 

크게 이 3가지입니다

 

이번 포스트에서는 node랑 proxy 셋팅하는 부분만 넣고

 

react부분은 다음 포스트에 적을 예정입니다

 

proxy setup

const {createProxyMiddleware} = require('http-proxy-middleware')

module.exports = function (app) {
    app.use(
        createProxyMiddleware('/api',{
            target: 'http://localhost:8080/',
            changeOrigin: true
        })
    )
}

http prox middleware를 사용해서 /api경로에 8080포트를 넣어줍니다

 

이 작업을 하게되면 3000포트 사용시 localhost:3000/api로 8080/api에 접근이 가능합니다

(8080도 사용 가능합니다)

 

< 3000포트로 접근 >

 

db helper

const sqlite3 = require('sqlite3').verbose()

const db = new sqlite3.Database('./server/db/task.db')

const create = () => {
    db.run('create table if not exists task(id integer, title text, day text, reminder integer, hide integer)', (err) => {
        if (err) {
            console.log(err.message)
            return
        }

        console.log('db created')
        // for(let i = 1 ; i < 45 ; i++){
        //     insert({ id: i, title: `Test - ${i}`, day: `test date - ${i}`, reminder: Math.random() < 0.4, hide: Math.random() < 0.2 })
        // }
    })
}

create()

const insert = async (task) => {
    return new Promise((res, rej) => {
        db.run(`insert into task(id,title,day,reminder,hide) values (?,?,?,?,?)`, [task.id, task.title, task.day, task.reminder ? 1 : 0, task.hide ? 1 : 0], (err) => {
            if (err) {
                console.log(err.message)
                return rej(err)
            }

            console.log(`insert task(${JSON.stringify(task)})`)
            return res(true)
        })
    })
}

const getLength = async () => {
    return new Promise((res, rej) => {
        return db.get('select count(*) from task where hide = 0', (err, row) => {
            if (err) {
                console.log(err.message)
                return rej(err)
            }

            console.log(`length : ${row['count(*)']}`)
            return res(row['count(*)'])
        })
    })
}

const getAllTasks = async () => {
    return new Promise((res, rej) => {
        return db.all('select * from task where hide = 0 order by id desc', (err, rows) => {
            if (err) {
                console.log(err.message)
                return rej(err)
            }

            console.log(`get all result : ${rows?.length}`)

            rows.forEach((e, i) => {
                rows[i].reminder = e.reminder === 1
                rows[i].hide = e.hide === 1
            })

            return res(rows)
        })
    })
}

const getSingleTask = async (id) => {
    return new Promise((res, rej) => {
        const sql = `select * from task where id = ${id} and hide = 0`

        return db.get(sql, (err, row) => {
            if (err) {
                console.log(err.message)
                return rej(err)
            }

            if (row) {
                row.reminder = row?.reminder === 1
                row.hide = row?.hide === 1
            }

            console.log(`get single result : ${JSON.stringify(row)}`)
            return res(row)
        })
    })
}

const getRange = async (offset, limit) => {
    return new Promise((res, rej) => {
        const sql = `select * from task where hide = 0 order by id desc limit ${limit} offset ${offset}`

        return db.all(sql, (err, rows) => {
            if (err) {
                console.log(err.message)
                return rej(err)
            }

            rows.forEach((e, i) => {
                rows[i].reminder = e.reminder === 1
                rows[i].hide = e.hide === 1
            })

            console.log(`get range result : ${rows.length}`)
            return res(rows)
        })
    })
}

const updateTask = async (key, value, index) => {
    return new Promise((res, rej) => {
        const sql = `update task set ${key} = ${value ? 1 : 0} where id = ${index}`
        return db.run(sql, (err) => {
            if (err) {
                console.log(err.message)
                return rej(err)
            }


            console.log(`update at ${index} => ${key} : ${value}`)
            return res(true)
        })
    })
}

module.exports = {
    create,
    insert,
    getAllTasks,
    updateTask,
    getRange,
    getSingleTask,
    getLength
}

간단한 부분은 넘어가고 필자가 고민했던 부분들만 간단히 말씀드려보자면

 

먼저 Promise를 굳이 사용한 이유는 sqlite3가 비동기가 아니어서

 

별도로 처리를 해줘야된다고 해서 이러면 되지 않을까? 해서 한번 해봤는데 다행이도 잘 작동을 하더군요

 

그리고 pagination구현을 하는데 js로 계산해서 넣다보니 가려지는값(hide = true)들 때문에

 

불러오는 갯수가 일정치 않아서 limit offset을 활용해봤습니다

 

getRange 부분을 간단히 보면

select * from task where hide = 0 order by id desc limit ${limit} offset ${offset}

 

hide가 0인곳(hide = false)을 내림차순으로 가져오고 (ex : 100 ~ 1)

 

offset부터 최대 limit까지 가져옴 인데

 

간단하게 예를 들어보자면

 

ex1) offset이 1이고 limit이 4이면 100부터 97까지

ex2) offset이 4이고 limit이 8이면 97부터 89까지

 

추가로 db.run(), db.get()등 메소드 괄호 내부에 `~ ${param} ~` 이런식으로 넣으면

 

에러가 발생해서 따로 sql 필드를 만들어서 넣어줘야되는것 같습니다..

(이거 몰라서 내가 뭘 잘못했지 하면서 시간낭비 + 자괴감이 많이 발생했습니다..)

 

routes > index

const express = require('express');
const router = express.Router();

const db = require('../db/db_helper')

const validateData = (data, type) => {
    if (typeof data != type || typeof data == 'undefined' || data === '') {
        console.log('Failed 1', {
            1: typeof data != type,
            2: typeof data == 'undefined',
            3: data === ''
        })
        return false
    }

    if (typeof data == 'object' && Object.keys(data).length == 0) {
        console.log('Failed 2', {
            1: typeof data == 'object',
            2: Object.keys(data).length == 0
        })
        return false
    }

    return true
}

router.get('/task', async (req, res) => {
    console.log('get all tasks')

    const pageNo = req.query.pageNo
    const pageSize = req.query.pageSize ?? 12

    let list

    let totalCount

    console.log('start')

    try {
        totalCount = await db.getLength() ?? 0
    } catch (error) {
        return res.send({ data: error, code: -1 })
    }

    if (!pageNo) {
        try {
            list = await db.getAllTasks()
        } catch (error) {
            return res.send({ data: error, code: -1 })
        }

        res.send({ data: list, code: 1, currentPage: -1, totalCount: totalCount })
    } else {
        // 전체 페이지수가 요청수보다 적을 때
        if (pageSize > totalCount) {
            try {
                list = await db.getAllTasks()
            } catch (error) {
                return res.send({ data: error, code: -1 })
            }

            return res.send({ data: { list: list, currentPage: Number(pageNo), totalCount: totalCount }, code: 1 })
        }

        try {
            list = await db.getRange((pageNo - 1) * pageSize, pageSize)
        } catch (error) {
            return res.send({ data: error, code: -1 })
        }

        res.send({ data: { list: list, currentPage: Number(pageNo), totalCount: totalCount }, code: 1 })
    }
})

router.get('/task/:no', async (req, res) => {
    let task

    try {
        task = await db.getSingleTask(req.params.no)
    } catch (error) {
        return res.send({ data: error, code: -1 })
    }

    console.log('get task', task)

    if (task) {
        res.send({ data: task, code: 1 })
    } else {
        res.send({ data: "No data", code: -1 })
    }
})

router.post('/task/add', async (req, res) => {
    const data = req.body

    console.log('task add', data)

    if (validateData(data.title, 'string') && validateData(data.day, 'string') && validateData(data.reminder, 'boolean')) {
        let id

        try {
            id = await db.getLength() ?? 1
        } catch (error) {
            return res.send({ data: error, code: -1 })
        }

        const newTest = { id: id + 1, ...data }

        let isDone

        try {
            isDone = await db.insert(newTest)
        } catch (error) {
            return res.send({ data: error, code: -1 })
        }

        if (isDone) {
            res.send({ data: newTest, code: 1 })
        } else {
            res.send({ data: `Request failed. (DB insert failed)`, code: -1 })
        }
    } else {
        res.send({ data: `Request failed. (${JSON.stringify(data)})`, code: -1 })
    }
})

router.post('/task/delete', async (req, res) => {
    const data = req.body
    console.log('task delete', data)

    let task
    
    try {
        task = await db.getSingleTask(data.id)
    } catch (error) {
        return res.send({ data: error, code: -1 })
    }

    if (validateData(task, 'object')) {
        let isDone

        try {
            isDone = await db.updateTask('hide', true, task.id)
        } catch (error) {
            return res.send({ data: error, code: -1 })
        }

        if (isDone) {
            res.send({ data: task.id, code: 1 })
        } else {
            res.send({ data: `Request failed. (DB update failed)`, code: -1 })
        }
    } else {
        res.send({ data: `Request failed. (${JSON.stringify(data)})`, code: -1 })
    }
})

router.post('/task/toggle', async (req, res) => {
    const data = req.body
    console.log('task toggle', data)

    let task
    
    try {
        task = await db.getSingleTask(data.id)
    } catch (error) {
        return res.send({ data: error, code: -1 })
    }

    if (validateData(task, 'object')) {
        let isDone

        try {
            isDone = await db.updateTask('reminder', !task.reminder, task.id)
        } catch (error) {
            return res.send({ data: error, code: -1 })
        }

        if (isDone) {
            res.send({ data: task.id, code: 1 })
        } else {
            res.send({ data: `Request failed. (DB update failed)`, code: -1 })
        }
    } else {
        res.send({ data: `Request failed. (${JSON.stringify(data)})`, code: -1 })
    }
})

module.exports = router

db helper에 만든 형식에 맞춰서 수정을 조금(아마도 많이..) 해줬는데

 

여기는 크게 달라지는 부분이 없으니 get task부분만 조금 보고 넘어가자면

 

쿼리스트링을 사용할 수 있게 만들었는데

 

사용 가능한것은 두가지로 pageNo, pageSize입니다

 

그리고 db에 저장되어있는 length를 한번 가져와서

 

요청한 사이즈보다 length가 적으면 전부다 보여주게 만들었는데

 

생각해보니 pageNo에 이상한 값 들어가도 무조건 다 들고오게 했는데

 

아마도 변경해야될것 같습니다

 

추가로 try catch를 안해주면 task/a 같이 이상한 값(?)이 들어오면

 

에러를 뿌리면서 서버가 죽는데, 이부분을 처리해주기 위해 해봤습니다

 

server

const express = require('express')

const app = express()

const cors = require('cors')
const router = require('./routes')

require('./db/db_helper')

const port = process.env.PORT || 8080

app.use(express.json())
app.use(express.urlencoded({ extended: true }))
app.use(cors())

app.use('/api', router)

// 8000 포트로 서버 오픈
app.listen(port, () => {
    console.log(`start! express server on port ${port}`)
})

 

서버는 db helper를 require을 사용해서 가져오면서

 

db create를 해주고 router를 이용해서 /api를 사용할 수 있게 만들었습니다

 

백단은 많이 안해봐서 아직 미숙하고 계정관련 처리는 아직 안해봤지만

 

백엔드의 컨셉을 이해하는정도(?)는 해본것 같고 하면서 재밌게 했었던것 같습니다

 

반응형
Comments