2022-12-03 PWA Night Conference 2022
Yusuke Wada
Today’s topics
with D1 and Hono
Making a Blog site
yarn init -y
yarn add -D wrangler
yarn wrangler init -y
yarn add hono
package.json
"scripts": {
"dev": "wrangler dev src/index.tsx",
"deploy": "wrangler publish src/index.tsx",
"test": "jest"
}
tsconfig.json
{
"compilerOptions": {
"lib": [
"ESNext"
],
"types": [
"jest",
"@cloudflare/workers-types"
],
"jsx": "react",
"jsxFactory": "jsx",
"jsxFragmentFactory": "Fragment"
},
"include": [
"src/**/*",
"tests/**/*"
]
}
src/index.tsx
import { Hono } from 'hono'
const app = new Hono()
app.get('/hello', (c) => c.text('Hello PWA night!'))
export default app
yarn dev
open http://localhost:8787
yarn deploy
👍
👍
Model
type Post = {
title: string
body: string
}
const posts: Post[] = []
Logic
const createPost = (post: Post) => {
posts.push(post)
}
const getPosts = () => {
return posts
}
Validator Middleware
import { validator } from 'hono/validator'
app.post('/post')
app.post(
'/post',
validator((v) => ({
title: v.body('title').isRequired(),
body: v.body('body').isRequired(),
})),
(c) => {
const { title, body } = c.req.valid()
createPost({ title, body })
return c.redirect('/')
}
)
TSX
touch src/Layout.tsx
touch src/Top.tsx
Layout.tsx
import { html } from 'hono/html'
export const Layout = (props: any) => html`<!DOCTYPE html>
<html>
<head>
<title>Hello D1!</title>
<link rel="stylesheet" href="https://unpkg.com/@picocss/pico@latest/css/pico.min.css" />
</head>
<body>
<body>
<main class="container">${props.children}</main>
</body>
</body>
</html>`
Top.tsx
import { jsx } from 'hono/jsx'
import { Layout } from './Layout'
import type { Post } from './index'
const Form = () => {
return (
<form action='/post' method='POST'>
<label>
Title:
<input name='title' />
</label>
<label>
Body:
<textarea name='body' rows='5' cols='33'></textarea>
</label>
<input type='submit' />
</form>
)
}
export const Top = (props: { posts: Post[] }) => {
return (
<Layout>
<h1>
<a href='/'>Hello D1!</a>
</h1>
<Form />
<hr />
{props.posts.reverse().map((post) => {
return (
<article>
<h2>{post.title}</h2>
<p>{post.body}</p>
</article>
)
})}
</Layout>
)
}
app.get('/')
import { jsx } from 'hono/jsx'
import { Top } from './Top'
//...
app.get('/', (c) => {
const posts = getPosts()
return c.html(<Top posts={posts} />)
})
yarn dev
yarn deploy
👍
blog.sql
CREATE TABLE post (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
body TEXT NOT NULL
);
Create Database
wrangler d1 create pwa-night
wrangler.toml
[[ d1_databases ]]
binding = "DB"
database_name = "pwa-night"
database_id = ""
preview_database_id = ""
wrangler d1 execute pwa-night --file blog.sql
wrangler d1 execute pwa-night --command "SELECT name FROM sqlite_schema WHERE type ='table'"
👍
Bindings
interface Env {
DB: D1Database
}
const app = new Hono<{ Bindings: Env }>()
app.get('/')
const { results } = await c.env.DB.prepare(
`SELECT id,title,body FROM post;`
).all<Post>()
const posts = results
app.post('/post')
await c.env.DB.prepare(`INSERT INTO post(title, body) VALUES(?, ?);`)
.bind(title, body)
.run()
yarn dev
yarn deploy
👍
yarn add -D jest jest-environment-miniflare @types/jest esbuild-jest
jest.config.js
module.exports = {
testEnvironment: 'miniflare',
testMatch: ['**/tests/**/*.+(ts|tsx)'],
transform: {
'^.+\\.(ts|tsx)$': 'esbuild-jest',
},
}
tests/index.ts
import app from '../src/index'
describe('Test endpoints', () => {
it('Should return 200 Response', async () => {
const res = await app.request('http://localhost/hello')
expect(res.status).toBe(200)
expect(await res.text()).toBe('Hello!')
})
})