Home [Devlog] Golang으로 게시판을 구현해보았다(1)
Post
Cancel

[Devlog] Golang으로 게시판을 구현해보았다(1)

[Devlog] Golang으로 게시판을 구현해보았다(1)

#2021-05-28

아직 완성 짓지 못한 프로젝트이긴 한데 아주 기본적인 기능들은 구현이 된 것 같아 중간 정리를 해보려 한다.
프로젝트 아이디어가 떠오르지 않아 뭘 해야 하나 고민하던 찰나에 남들 다 구현할 줄 안다는 게시판,, (솔직히 대충 머리로 상상 코딩해놓고 게시판 정도는 쉽겠지라고 생각했다가 만들면서 새로 알게 된 점이 많았다 ㅋㅋㅠㅠ)을 만들어 보기로 했다.
스펙을 나열해보자면

  • 프론트: Go templates
  • 백엔드: Golang, Gorm + mysql
  • 배포: AWS EC2, RDS

요정도 되겠다.. 먼저 목차를 정리해보자.

목차

  1. ORM
  2. 글 쓰기
  3. 글 목록 불러오기 + 페이징
  4. 글 검색 (제목, 작성자)
  5. 글 수정 및 삭제
  6. 앞으로의 계획

순으로 정리해보겠다. 쓰다보니 양이 많아질 것 같아 이번 글에서는 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)
}

targetauthor인 부분을 뺐는데도 상당히 길다;;;
아무튼 보면 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/ (최신버전이 아닐 수 있음.)

This post is licensed under CC BY 4.0 by the author.

[Go/embed] embed 패키지로 배포를 더 쉽게

[Go] .env로 환경변수 설정하기