[Devlog] Golang으로 게시판을 구현해보았다(1)
#2021-05-28
아직 완성 짓지 못한 프로젝트이긴 한데 아주 기본적인 기능들은 구현이 된 것 같아 중간 정리를 해보려 한다.
프로젝트 아이디어가 떠오르지 않아 뭘 해야 하나 고민하던 찰나에 남들 다 구현할 줄 안다는 게시판,, (솔직히 대충 머리로 상상 코딩해놓고 게시판 정도는 쉽겠지라고 생각했다가 만들면서 새로 알게 된 점이 많았다 ㅋㅋㅠㅠ)을 만들어 보기로 했다.
스펙을 나열해보자면
- 프론트: Go templates
- 백엔드: Golang, Gorm + mysql
- 배포: AWS EC2, RDS
요정도 되겠다.. 먼저 목차를 정리해보자.
목차
- ORM
- 글 쓰기
- 글 목록 불러오기 + 페이징
- 글 검색 (제목, 작성자)
- 글 수정 및 삭제
- 앞으로의 계획
순으로 정리해보겠다. 쓰다보니 양이 많아질 것 같아 이번 글에서는 3. 글 목록 불러오기 + 페이징
까지만 정리하겠다.
ORM
우선 이 프로젝트를 하면서 ORM을 새롭게 사용해 보게 되었다.
ORM이란 Object Relational Mapping으로 직역하면 객체-관계 매핑을 뜻하며 따로 쿼리문을 작성하지 않고도 데이터 베이스를 조작할 수 있게 해주어 생산성과 가독성을 높여주는 친구이다(이전엔 쿼리문을 작성하면서 했었는데 orm을 써보니 정말 신세계였다.. ).
나는 Go 언어의 orm 패키지인 gorm
패키지를 가져와 사용하였다.
아래 코드처럼 칼럼 값을 가진 Board
구조체를 선언해 준 뒤
1
gormDB.AutoMigrate(&Board{})
행이 실행되면 데이터베이스에 Board
구조체와 같은 구조의 테이블이 존재하면 연결, 그렇지 않다면 새 테이블을 생성해 준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type Board struct {
ID uint `gorm:"primarykey"`
CreatedAt time.Time
UpdatedAt time.Time
Title string
Author string
Content string
}
func main() {
var connectionString = fmt.Sprintf("%s:%s@tcp(%s:3306)/%s?charset=utf8mb4&parseTime=True", user, password, host, database)
mysqlDB, err := sql.Open("mysql", connectionString)
defer mysqlDB.Close()
gormDB, err = gorm.Open(mysql.New(mysql.Config{
Conn: mysqlDB,
}), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
gormDB.AutoMigrate(&Board{})
}
이렇게 데이터베이스를 조작해주는 gormDB
객체를 생성해주었고 어떻게 쓰일지는 뒤에서 알아가보도록 하자.
글 쓰기
일단 메인 페이지는 아래처럼 생겨먹었다.
게시판 프로젝트에서 글을 쓴다는 것은 사용자가 어떤 양식에 콘텐츠를 입력하면 그 입력값을 데이터베이스에 추가를 해주는 행위라고 볼 수 있다. 그래서 나는 메인 페이지에서 글쓰기
버튼을 누르면 /write
로 연결해 주었고 /write
에서는 사용자의 입력값을 받아 DB에 올리는 작업을 수행해 주었다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func main() {
...
http.HandleFunc("/write", write)
...
}
func write(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
title := r.PostFormValue("title")
author := r.PostFormValue("author")
content := r.PostFormValue("content")
newPost := Board{Title: title, Author: author, Content: content}
gormDB.Create(&newPost)
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
tpl.ExecuteTemplate(w, "write.gohtml", nil)
}
/write
로 이동하게 되면 tpl.ExecuteTemplate()
함수로 글을 쓰는 템플릿을 사용자에게 제공해 주었고 입력을 마친 뒤 글쓰기
버튼을 누르면 서버로 post
신호를 보내 if 문 블럭이 실행되어 입력값을 토대로 DB에 행을 추가해 주게 하였다.
글 목록 불러오기
글 목록을 불러오는 것은 DB에서 행을 불러와 출력해 주면 되었는데 단순히 불러오기만 하면 되는 게 아니라 페이징도 구현해야 했고 검색기능을 통해 글 목록을 불러올 때 어떻게 페이징을 구현해 주어야 하나 고민을 좀 했던 것 같다.
처음엔 현재 페이지, 검색 키워드 등을 토대로 일일이 페이지 링크를 구현해 주어야 하나 싶어서 혹시 더 좋은 방법은 없는지 다른 홈페이지(op.gg, inven, 네이버 카페) 등을 둘러보았는데 다행히 내가 생각했던 것과 비슷하게 각 페이지 버튼(링크?)마다 /board/?page=3&target=제목&value=안녕
형식으로 링크가 걸려있어 내 생각대로 구현을 해 보았다.
아직 이 부분 코드 리팩토링을 하지 않아 중복되는 부분이 많고 더 줄일 수 있는 여지가 많이 남아있다..
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
func board(w http.ResponseWriter, r *http.Request) {
var b []Board
page := r.FormValue("page")
if page == "" {
page = "1"
}
pageInt, _ := strconv.Atoi(page)
if keyword := r.FormValue("v"); keyword != "" {
target := r.FormValue("target")
switch target {
case "title":
q := gormDB.Where("title LIKE ?", fmt.Sprintf("%%%s%%", keyword)).Find(&b)
pg := paginator.New(adapter.NewGORMAdapter(q), MaxPerPage)
pg.SetPage(pageInt)
if err := pg.Results(&b); err != nil {
panic(err)
}
pgNums, _ := pg.PageNums()
pageSlice := getPageList(page, pgNums)
temp := PassedData{
PostData: b,
Target: target,
Value: keyword,
PageList: pageSlice,
Page: page,
}
tpl.ExecuteTemplate(w, "board.gohtml", temp)
return
q := gormDB.Order("id desc").Find(&b)
pg := paginator.New(adapter.NewGORMAdapter(q), MaxPerPage)
pg.SetPage(pageInt)
if err := pg.Results(&b); err != nil {
panic(err)
}
pgNums, _ := pg.PageNums()
pageSlice := getPageList(page, pgNums)
temp := PassedData{
PostData: b,
PageList: pageSlice,
Page: page,
}
tpl.ExecuteTemplate(w, "board.gohtml", temp)
}
target
이 author
인 부분을 뺐는데도 상당히 길다;;;
아무튼 보면 paginator
객체를 쓴 걸 확인할 수 있는데 이는 쿼리 결괏값을 토대로 페이징을 구현해 주는 패키지이다.
쿼리 결과와 max per page
를 인자로 주고 pg.SetPage(원하는 페이지)
, pg.Results(&board)
를 해주면 board
에 해당 페이지의 게시글 정보가 들어가게 된다.
이렇게 /board
라우터에 query parameter가 주어지면 해당 파라미터 값에 따라 게시글을 보여주고 query parameter가 주어지지 않으면 전체 게시글 목록을 보여주었다.
페이징의 경우 template에 현재 페이지, 현재 페이지+-2 값을 보내주어 받아온 데이터를 토대로 링크 값을 생성해 페이지를 나열해 주었다.
이렇게 해서 생성된 페이지의 모습이다. 나름 그럴싸…해보인다 ㅎㅎ…
이렇게 페이징을 구현하였고 1
, 3
에 커서를 올리면 localhost:8080/board/?target=&v=&page=1
이런식으로 되어있다. 이렇게 글쓰기
, 글 목록 불러오기
, 페이징
을 구현하였고 다음 글에서는 검색기능
, 수정 및 삭제
등등… 에 대한 내용을 준비해오겠다.
전체 코드: https://github.com/j1mmyson/board_generator
데모 링크: http://ec2-3-17-39-222.us-east-2.compute.amazonaws.com/ (최신버전이 아닐 수 있음.)