mirror of
https://github.com/syumai/workers.git
synced 2025-03-10 09:27:08 +00:00
add mysql-blog-server example
This commit is contained in:
parent
4789241e05
commit
30569739ef
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@ dist
|
||||
build
|
||||
node_modules
|
||||
.wrangler
|
||||
.dev.vars
|
||||
|
1
_examples/mysql-blog-server/.dev.vars.example
Normal file
1
_examples/mysql-blog-server/.dev.vars.example
Normal file
@ -0,0 +1 @@
|
||||
MYSQL_DSN=user:pass@tcp(hostname)/database-name?interpolateParams=true
|
3
_examples/mysql-blog-server/.gitignore
vendored
Normal file
3
_examples/mysql-blog-server/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
build
|
||||
node_modules
|
||||
.wrangler
|
12
_examples/mysql-blog-server/Makefile
Normal file
12
_examples/mysql-blog-server/Makefile
Normal file
@ -0,0 +1,12 @@
|
||||
.PHONY: dev
|
||||
dev:
|
||||
wrangler dev
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
go run ../../cmd/workers-assets-gen -mode=go
|
||||
GOOS=js GOARCH=wasm go build -o ./build/app.wasm .
|
||||
|
||||
.PHONY: deploy
|
||||
deploy:
|
||||
wrangler deploy
|
74
_examples/mysql-blog-server/README.md
Normal file
74
_examples/mysql-blog-server/README.md
Normal file
@ -0,0 +1,74 @@
|
||||
# mysql-blog-server
|
||||
|
||||
* A simple Blog server implemented in Go.
|
||||
* This example is using MySQL.
|
||||
|
||||
# WIP
|
||||
|
||||
### Create blog post
|
||||
|
||||
```
|
||||
$ curl -X POST 'http://localhost:8787/articles' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"title":"example post",
|
||||
"body":"body of the example post"
|
||||
}'
|
||||
{
|
||||
"article": {
|
||||
{
|
||||
"id": "f9e8119e-881e-4dc5-9307-af4f2dc79891",
|
||||
"title": "example post",
|
||||
"body": "body of the example post",
|
||||
"createdAt": 1677382874
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### List blog posts
|
||||
|
||||
```
|
||||
$ curl 'http://localhost:8787/articles'
|
||||
{
|
||||
"articles": [
|
||||
{
|
||||
"id": "bea6cd80-5a83-45f0-b061-0e13a2ad5fba",
|
||||
"title": "example post 2",
|
||||
"body": "body of the example post 2",
|
||||
"createdAt": 1677383758
|
||||
},
|
||||
{
|
||||
"id": "f9e8119e-881e-4dc5-9307-af4f2dc79891",
|
||||
"title": "example post",
|
||||
"body": "body of the example post",
|
||||
"createdAt": 1677382874
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Requirements
|
||||
|
||||
This project requires these tools to be installed globally.
|
||||
|
||||
* wrangler
|
||||
* go
|
||||
|
||||
### Setup MySQL DB
|
||||
|
||||
* This project requires MySQL DB.
|
||||
- Connection setting: `.dev.vars.example` (please rename to `.dev.vars`.)
|
||||
- Initial migration SQL: `schema.sql`
|
||||
* If you want to deploy this app to production, please set `MYSQL_DSN` to your Worker secrets.
|
||||
- Run: `npx wrangler secret put MYSQL_DSN`.
|
||||
|
||||
### Commands
|
||||
|
||||
```
|
||||
make dev # run dev server
|
||||
make build # build Go Wasm binary
|
||||
make deploy # deploy worker
|
||||
```
|
135
_examples/mysql-blog-server/app/handler.go
Normal file
135
_examples/mysql-blog-server/app/handler.go
Normal file
@ -0,0 +1,135 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/go-sql-driver/mysql"
|
||||
"github.com/syumai/workers/_examples/mysql-blog-server/app/model"
|
||||
"github.com/syumai/workers/cloudflare"
|
||||
"github.com/syumai/workers/cloudflare/sockets"
|
||||
)
|
||||
|
||||
type articleHandler struct{}
|
||||
|
||||
var _ http.Handler = (*articleHandler)(nil)
|
||||
|
||||
func NewArticleHandler() http.Handler {
|
||||
return &articleHandler{}
|
||||
}
|
||||
|
||||
func (h *articleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// initialize DB.
|
||||
mysql.RegisterDialContext("tcp", func(_ context.Context, addr string) (net.Conn, error) {
|
||||
return sockets.Connect(req.Context(), addr, &sockets.SocketOptions{
|
||||
SecureTransport: sockets.SecureTransportOff,
|
||||
})
|
||||
})
|
||||
db, err := sql.Open("mysql",
|
||||
cloudflare.Getenv(req.Context(), "MYSQL_DSN"))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to connect: %v", err)
|
||||
}
|
||||
|
||||
switch req.Method {
|
||||
case http.MethodGet:
|
||||
h.listArticles(w, req, db)
|
||||
return
|
||||
case http.MethodPost:
|
||||
h.createArticle(w, req, db)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Write([]byte("not found"))
|
||||
}
|
||||
|
||||
func (h *articleHandler) handleErr(w http.ResponseWriter, status int, msg string) {
|
||||
w.WriteHeader(status)
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Write([]byte(msg))
|
||||
}
|
||||
|
||||
func (h *articleHandler) createArticle(w http.ResponseWriter, req *http.Request, db *sql.DB) {
|
||||
var createArticleReq model.CreateArticleRequest
|
||||
if err := json.NewDecoder(req.Body).Decode(&createArticleReq); err != nil {
|
||||
h.handleErr(w, http.StatusBadRequest,
|
||||
"request format is invalid")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
article := model.Article{
|
||||
Title: createArticleReq.Title,
|
||||
Body: createArticleReq.Body,
|
||||
CreatedAt: uint64(now),
|
||||
}
|
||||
|
||||
result, err := db.Exec(`
|
||||
INSERT INTO articles (title, body, created_at)
|
||||
VALUES (?, ?, ?)
|
||||
`, article.Title, article.Body, article.CreatedAt)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
h.handleErr(w, http.StatusInternalServerError,
|
||||
"failed to save article")
|
||||
return
|
||||
}
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
h.handleErr(w, http.StatusInternalServerError,
|
||||
"failed to get ID of inserted article")
|
||||
return
|
||||
}
|
||||
article.ID = uint64(id)
|
||||
|
||||
res := model.CreateArticleResponse{
|
||||
Article: article,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(res); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to encode response: %w\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *articleHandler) listArticles(w http.ResponseWriter, req *http.Request, db *sql.DB) {
|
||||
rows, err := db.Query(`
|
||||
SELECT id, title, body, created_at FROM articles
|
||||
ORDER BY created_at DESC;
|
||||
`)
|
||||
if err != nil {
|
||||
h.handleErr(w, http.StatusInternalServerError,
|
||||
"failed to load article")
|
||||
return
|
||||
}
|
||||
|
||||
articles := []model.Article{}
|
||||
for rows.Next() {
|
||||
var a model.Article
|
||||
err = rows.Scan(&a.ID, &a.Title, &a.Body, &a.CreatedAt)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
h.handleErr(w, http.StatusInternalServerError,
|
||||
"failed to scan article")
|
||||
return
|
||||
}
|
||||
articles = append(articles, a)
|
||||
}
|
||||
res := model.ListArticlesResponse{
|
||||
Articles: articles,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(res); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to encode response: %w\n", err)
|
||||
}
|
||||
}
|
21
_examples/mysql-blog-server/app/model/article.go
Normal file
21
_examples/mysql-blog-server/app/model/article.go
Normal file
@ -0,0 +1,21 @@
|
||||
package model
|
||||
|
||||
type Article struct {
|
||||
ID uint64 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
CreatedAt uint64 `json:"createdAt"`
|
||||
}
|
||||
|
||||
type CreateArticleRequest struct {
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
type CreateArticleResponse struct {
|
||||
Article Article `json:"article"`
|
||||
}
|
||||
|
||||
type ListArticlesResponse struct {
|
||||
Articles []Article `json:"articles"`
|
||||
}
|
10
_examples/mysql-blog-server/go.mod
Normal file
10
_examples/mysql-blog-server/go.mod
Normal file
@ -0,0 +1,10 @@
|
||||
module github.com/syumai/workers/_examples/mysql-blog-server
|
||||
|
||||
go 1.21.3
|
||||
|
||||
require (
|
||||
github.com/go-sql-driver/mysql v1.7.1
|
||||
github.com/syumai/workers v0.9.0
|
||||
)
|
||||
|
||||
replace github.com/syumai/workers => ../../
|
2
_examples/mysql-blog-server/go.sum
Normal file
2
_examples/mysql-blog-server/go.sum
Normal file
@ -0,0 +1,2 @@
|
||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
13
_examples/mysql-blog-server/main.go
Normal file
13
_examples/mysql-blog-server/main.go
Normal file
@ -0,0 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/syumai/workers"
|
||||
"github.com/syumai/workers/_examples/mysql-blog-server/app"
|
||||
)
|
||||
|
||||
func main() {
|
||||
http.Handle("/articles", app.NewArticleHandler())
|
||||
workers.Serve(nil) // use http.DefaultServeMux
|
||||
}
|
13
_examples/mysql-blog-server/schema.sql
Normal file
13
_examples/mysql-blog-server/schema.sql
Normal file
@ -0,0 +1,13 @@
|
||||
DROP TABLE IF EXISTS articles;
|
||||
CREATE TABLE IF NOT EXISTS articles (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
body TEXT NOT NULL,
|
||||
created_at INT NOT NULL
|
||||
);
|
||||
CREATE INDEX idx_articles_on_created_at ON articles (created_at DESC);
|
||||
INSERT INTO articles (title, body, created_at) VALUES (
|
||||
'title of example post',
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
|
||||
UNIX_TIMESTAMP()
|
||||
);
|
6
_examples/mysql-blog-server/wrangler.toml
Normal file
6
_examples/mysql-blog-server/wrangler.toml
Normal file
@ -0,0 +1,6 @@
|
||||
name = "mysql-blog-server"
|
||||
main = "./build/worker.mjs"
|
||||
compatibility_date = "2024-01-03"
|
||||
|
||||
[build]
|
||||
command = "make build"
|
Loading…
x
Reference in New Issue
Block a user