おさらい
前回は、サイト上からデータベースに保存されているデータを取得して表示するところまでやりました。
まずはHTTPというプロトコルの概要とContent-Typeというヘッダーについて説明しました。 このContent-Typeは、サーバーからクライアントに送信するデータの種類を表すもので、
text/html
: HTMLとして解釈されるtext/plain
: テキストとして解釈されるapplication/json
: JSONとして解釈される
のように、通信で受け取った(送った)データの種類を表すものです。 これを間違うと、ブラウザが正しくデータを解釈できないので、正しく設定する必要があります。
そしてそのあと、Content-Typeがtext/html
であれば別にHTMLファイルを作らずとも、普通のHTMLが書かれた文字列を送信することで、ブラウザがHTMLとして解釈してくれることを確認しました。
その挙動を利用して、実際にサーバー(前回はHonoを利用しました)からデータを取得して、サーバー上でHTML(文字列)を生成して、それをクライアントに送信するということをやりました。
この操作をサーバーサイドレンダリングもしくはSSRと呼びました。 SSRをすると簡単にサーバーからデータを取得して、それをHTMLに埋め込んで表示することができます。 しかし、SSRはサーバーの負荷が高くなるという欠点がありました。
今回やること
前回データベースに保存されているデータを取得してSSRして表示するところまでやりました。 でも、データベースに保存されているデータをサイト上から追加投稿したいですよね? 今回は、サイト上からデータベースに保存されているデータを追加投稿できるようにしていきます。
今回も前回の続きになりますので、前回の記事を終わらせてから進めてください。
最小のフォーム
今回は、一番ベーシックな「フォームを使う」方法で投稿を実装していきます。
フォームを使う方法は、サーバーにデータを送信するための方法の一つです。
フォームを使う方法は、HTMLの<form>
タグを使って実装します。
(これは例なのでプロジェクトにコピーしなくて大丈夫です)
<form action="/post" method="POST">
<input type="text" name="title" />
<input type="text" name="content" />
<button type="submit">投稿</button>
</form>
このように、<form>
タグのaction
属性には、データを送信する先のURLを指定します。たとえば、/post
でサーバーが受け取れるようにしてあるなら、action="/post"
とします。
POST
というのは、HTTPのメソッドの一つで、データを送信するときに使います。
Honoでは、GETメソッドの通信にapp.get
を使いましたが、POSTメソッドの通信にはapp.post
を使うことになります。
そして、<input>
タグのtype
属性には、入力するデータの種類を指定します。今回は、type="text"
にしています。
他にも、type="password"
とするとパスワードを入力できたり、type="number"
とすると数字を入力できたりします。
そして、<input>
タグのname
属性には、送信するデータの名前を指定します。
今回は、name="title"
とname="content"
にしています。
最後に、<button>
タグのtype
属性には、ボタンの種類を指定します。今回は、type="submit"
にしています。
type="submit"
にすると、ボタンを押したときにフォームのデータを送信することができます。
より実践的なフォーム
ラベルをつける
最小のフォームを作ることができたので、より実践的なフォームを作っていきます。 先ほどの画像のような感じではどの入力フィールドに何を入力すればいいのかわからないのですよね。 これでは使いづらいので、入力フィールドの上にラベルをつけてあげましょう。
<form action="/post" method="POST">
<label for="title">タイトル</label>
<input type="text" name="title" id="title" />
<label for="content">内容</label>
<input type="text" name="content" id="content" />
<button type="submit">投稿</button>
</form>
<label>
タグのfor
属性には、そのラベルがついている入力フィールドのid
属性の値を指定します。
<input>
タグのid
属性には、その入力フィールドの名前を指定します。
こうすることで、入力フィールドとラベルが紐づけられます。
テキストエリアを使う
次に、入力フィールドを使うのではなく、テキストエリアを使ってみましょう。
<form action="/post" method="POST">
<label for="title">タイトル</label>
<input type="text" name="title" id="title" />
<label for="content">内容</label>
<textarea name="content" id="content" rows="10"></textarea>
<button type="submit">投稿</button>
</form>
<textarea>
も<input type="text">
とほぼ同じ使い方ができますが、<textarea>
は複数行のテキストを入力することができます。
<textarea>
タグのrows
属性には、テキストエリアの行数を指定します。これは最大行数ではなく初期表示の行数です。
プレイスホルダーを使う
次に、入力フィールドにプレイスホルダーをつけてみましょう。
Twitterのフォームもプレイスホルダーを使っているので、見たことがある人も多いと思います。これです。
<form action="/post" method="POST">
<label for="title">タイトル</label>
<input type="text" name="title" id="title" placeholder="タイトル" />
<label for="content">内容</label>
<textarea name="content" id="content" placeholder="内容" rows="10"></textarea>
<button type="submit">投稿</button>
</form>
<style>
input,
textarea {
width: 100%;
margin-bottom: 16px;
}
</style>
ちょっと見栄えが悪いので、CSSでちょっとだけ見栄えを良くしています。
<input>
タグと<textarea>
タグのplaceholder
属性には、プレイスホルダーの文字列を指定します。
プレイスホルダーとは、入力フィールドに何も入力されていないときに表示される文字列のことです。
プロジェクトの整理
投稿機能を作る前にHTML文字列を別ファイルに分けましょう。
前回作ったindex.js
の中のHTML
変数などの埋め込み文字列を別ファイルに分けて、index.js
から読み込むようにします。
ではtemplates.js
というファイルを作って、そこにHTML文字列を書いていきます。
ちなみに、今後こういったHTML文字列をテンプレートやビューと呼びます。
const HTML = (body) => `
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>これはただの文字列です</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
${body}
</body>
</html>
`;
const TWEET_LIST_VIEW = (tweets) => `
<h1 class="title">ツイート一覧</h1>
<div class="tweet-list">
${tweets
.map((tweet) => `<div class="tweet">${tweet.content}</div>`)
.join("\n")}
</div>
`;
module.exports = {
HTML,
TWEET_LIST_VIEW,
};
index.js
はこのように変えます。
HTML
変数が削除されているので注意してください。
const sqlite3 = require("sqlite3").verbose();
const queries = require("./queries");
const templates = require("./templates");
const { serve } = require("@hono/node-server");
const { serveStatic } = require("@hono/node-server/serve-static");
const { Hono } = require("hono");
const db = new sqlite3.Database("database.db");
db.serialize(() => {
db.run(queries.Tweets.createTable);
db.run(queries.Users.createTable);
db.run(queries.Users.create, 'りんご太郎', '[email protected]', '2022-08-15 00:00:00');
db.run(queries.Users.create, 'みかん次郎', '[email protected]', '2022-08-15 00:00:01');
db.run(queries.Users.create, 'ぶどう三郎', '[email protected]', '2022-08-15 00:00:02');
db.run(queries.Tweets.create, 'あけおめ!', 3, '2023-01-01 00:00:00');
db.run(queries.Tweets.create, '今年もよろしくお願いします!', 2, '2023-01-01 00:00:01');
db.run(queries.Tweets.create, '今年こそは痩せるぞ!', 1, '2023-01-01 00:00:02');
});
const app = new Hono();
app.get("/", async (c) => {
const tweets = await new Promise((resolve) => {
db.all(queries.Tweets.findAll, (err, rows) => {
resolve(rows);
});
});
const tweetList = templates.TWEET_LIST_VIEW(tweets);
const response = templates.HTML(tweetList);
return c.html(response);
});
app.use("/static/*", serveStatic({ root: "./" }));
serve(app);
process.stdin.on("data", (data) => {
if (data.toString().trim() === "q") {
db.close();
process.exit();
}
});
こんな感じで、HTML
変数をtemplates.js
に移動させて、index.js
から読み込むようにしました。
こうすることで、HTMLを組み立てる
という責務をindex.js
からtemplates.js
に移動させることができました。
この修正は動作を変えず、コードを整理するという変更なので、リファクタリングと呼びます。
ではコミットしましょう。
git add .
git commit -m "HTML文字列を別ファイルに分けてリファクタリングした"
ユーザー登録ができるようにする
ユーザー登録のためのフォームを作る
まずは、ユーザー登録のためのフォームを作ります。
const HTML = (body) => `
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>これはただの文字列です</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
${body}
</body>
</html>
`;
const TWEET_LIST_VIEW = (tweets) => `
<h1 class="title">ツイート一覧</h1>
<div class="tweet-list">
${tweets
.map((tweet) => `<div class="tweet">${tweet.content}</div>`)
.join("\n")}
</div>
`;
const USER_REGISTER_FORM_VIEW = () => `
<h1 class="title">ユーザー登録</h1>
<form action="/user/register" method="POST">
<label for="name">名前</label>
<input type="text" name="name" id="name" />
<label for="email">メールアドレス</label>
<input type="email" name="email" id="email" />
<button type="submit">登録</button>
</form>
`;
module.exports = {
HTML,
TWEET_LIST_VIEW,
USER_REGISTER_FORM_VIEW,
};
これで、ユーザー登録のためのフォームが作れました。
あとはこれをindex.js
から呼び出すだけです。
const sqlite3 = require("sqlite3").verbose();
const queries = require("./queries");
const templates = require("./template");
const { serve } = require("@hono/node-server");
const { serveStatic } = require("@hono/node-server/serve-static");
const { Hono } = require("hono");
const db = new sqlite3.Database("database.db");
db.serialize(() => {
db.run(queries.Tweets.createTable);
db.run(queries.Users.createTable);
db.run(queries.Users.create, 'りんご太郎', '[email protected]', '2022-08-15 00:00:00');
db.run(queries.Users.create, 'みかん次郎', '[email protected]', '2022-08-15 00:00:01');
db.run(queries.Users.create, 'ぶどう三郎', '[email protected]', '2022-08-15 00:00:02');
db.run(queries.Tweets.create, 'あけおめ!', 3, '2023-01-01 00:00:00');
db.run(queries.Tweets.create, '今年もよろしくお願いします!', 2, '2023-01-01 00:00:01');
db.run(queries.Tweets.create, '今年こそは痩せるぞ!', 1, '2023-01-01 00:00:02');
});
const app = new Hono();
app.get("/", async (c) => {
const tweets = await new Promise((resolve) => {
db.all(queries.Tweets.findAll, (err, rows) => {
resolve(rows);
});
});
const tweetList = templates.TWEET_LIST_VIEW(tweets);
const response = templates.HTML(tweetList);
return c.html(response);
});
app.get("/user/register", async (c) => {
const registerForm = templates.USER_REGISTER_FORM_VIEW();
const response = templates.HTML(registerForm);
return c.html(response);
});
app.post("/user/register", async (c) => {
const body = await c.req.parseBody();
const now = new Date().toISOString();
const userID = await new Promise((resolve) => {
db.run(queries.Users.create, body.name, body.email, now, function(err) {
resolve(this.lastID);
});
});
return c.redirect(`/user/${userID}`);
});
app.use("/static/*", serveStatic({ root: "./" }));
serve(app);
process.stdin.on("data", (data) => {
if (data.toString().trim() === "q") {
db.close();
process.exit();
}
});
これで/user/register
にアクセスすると、ユーザー登録のためのフォームが表示されます。
そして今回新たに/user/register
にPOST
メソッドでアクセスすると、ユーザー登録ができるようになりました。
なのでUSER_REGISTER_FORM_VIEW
で作ったフォームのaction
属性には/user/register
を指定しています。
parseBody
というのは、Formから送信されたデータを解釈するための関数です。
これを使うことで、name
やemail
といったデータを取得することができます。
redirect
というのは、リダイレクトをするための関数です。
リダイレクトとは、サーバーからクライアントに対して、別のURLにアクセスするように指示することです。
今回は、ユーザー登録が完了したら、そのユーザーのツイート一覧を表示するようにしています。
(このあとユーザーのツイート一覧を表示する処理を書きますので、今は404エラーが表示されます
できたら、/user/register
にアクセスして、ユーザー登録ができるか確認してみましょう。
まだユーザー登録がされているかどうかはサイト上では確認できませんが、データベースには登録されているので、確認してみましょう。
sqlite3 database.db
SELECT * FROM users;
ちゃんとユーザー登録ができていることが確認できました。
ではコミットしましょう。
git add .
git commit -m "ユーザー登録のためのフォームを作った"
特定のユーザーが投稿したツイート一覧を表示する
ユーザー登録ができるようになったので、ユーザーごとにツイート一覧を表示できるようにしていきます。 要件は
/user/:id
にアクセスすると、そのユーザーが投稿したツイート一覧が表示される- もし、そのユーザーが存在しない場合は、404エラーが表示される
という感じにしてみましょう。
まずは、templates.js
にユーザーのツイート一覧を表示するための関数を追加します。
const HTML = (body) => `
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>これはただの文字列です</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
${body}
</body>
</html>
`;
const TWEET_LIST_VIEW = (tweets) => `
<h1 class="title">ツイート一覧</h1>
<div class="tweet-list">
${tweets
.map((tweet) => `<div class="tweet">${tweet.content}</div>`)
.join("\n")}
</div>
`;
const USER_REGISTER_FORM_VIEW = () => `
<h1 class="title">ユーザー登録</h1>
<form action="/user/register" method="POST">
<label for="name">名前</label>
<input type="text" name="name" id="name" />
<label for="email">メールアドレス</label>
<input type="email" name="email" id="email" />
<button type="submit">登録</button>
</form>
`;
const USER_TWEET_LIST_VIEW = (user, tweets) => `
<h1 class="title">${user.name}さんのツイート一覧</h1>
<div class="tweet-list">
${tweets
.map((tweet) => `<div class="tweet">${tweet.content}</div>`)
.join("\n")}
</div>
`;
module.exports = {
HTML,
TWEET_LIST_VIEW,
USER_REGISTER_FORM_VIEW,
USER_TWEET_LIST_VIEW,
};
次に特定のユーザーが投稿したツイート一覧を表示するためのSQLをqueries.js
に追加します。
ユーザーIDから全てのツイートを取得するSQLと、ユーザーIDからユーザーを取得するSQLを追加します。
const Tweets = {
createTable: `
CREATE TABLE IF NOT EXISTS tweets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
content TEXT NOT NULL,
user_id INTEGER NOT NULL,
created_at DATETIME NOT NULL
);
`,
create: `INSERT INTO tweets (content, user_id, created_at) VALUES (?, ?, ?);`,
findAll: `SELECT * FROM tweets;`,
findByUserId: `SELECT * FROM tweets WHERE user_id = ?;`,
};
const Users = {
createTable: `
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL,
created_at DATETIME NOT NULL
);
`,
create: `INSERT INTO users (name, email, created_at) VALUES (?, ?, ?);`,
findAll: `SELECT * FROM users;`,
findById: `SELECT * FROM users WHERE id = ?;`,
findByTweetId: `SELECT * FROM users WHERE id = (SELECT user_id FROM tweets WHERE id = ?);`,
};
module.exports = {
Tweets,
Users,
};
そして、index.js
に/user/:id
にアクセスしたときの処理を追加します。
const sqlite3 = require("sqlite3").verbose();
const queries = require("./queries");
const templates = require("./template");
const { serve } = require("@hono/node-server");
const { serveStatic } = require("@hono/node-server/serve-static");
const { Hono } = require("hono");
const db = new sqlite3.Database("database.db");
db.serialize(() => {
db.run(queries.Tweets.createTable);
db.run(queries.Users.createTable);
db.run(queries.Users.create, 'りんご太郎', '[email protected]', '2022-08-15 00:00:00');
db.run(queries.Users.create, 'みかん次郎', '[email protected]', '2022-08-15 00:00:01');
db.run(queries.Users.create, 'ぶどう三郎', '[email protected]', '2022-08-15 00:00:02');
db.run(queries.Tweets.create, 'あけおめ!', 3, '2023-01-01 00:00:00');
db.run(queries.Tweets.create, '今年もよろしくお願いします!', 2, '2023-01-01 00:00:01');
db.run(queries.Tweets.create, '今年こそは痩せるぞ!', 1, '2023-01-01 00:00:02');
});
const app = new Hono();
app.get("/", async (c) => {
const tweets = await new Promise((resolve) => {
db.all(queries.Tweets.findAll, (err, rows) => {
resolve(rows);
});
});
const tweetList = templates.TWEET_LIST_VIEW(tweets);
const response = templates.HTML(tweetList);
return c.html(response);
});
app.get("/user/register", async (c) => {
const registerForm = templates.USER_REGISTER_FORM_VIEW();
const response = templates.HTML(registerForm);
return c.html(response);
});
app.post("/user/register", async (c) => {
const body = await c.req.parseBody();
const now = new Date().toISOString();
const userID = await new Promise((resolve) => {
db.run(queries.Users.create, body.name, body.email, now, function(err) {
resolve(this.lastID);
});
});
return c.redirect(`/user/${userID}`);
});
app.get("/user/:id", async (c) => {
const userId = c.req.param("id");
const user = await new Promise((resolve) => {
db.get(queries.Users.findById, userId, (err, row) => {
resolve(row);
});
});
if (!user) {
return c.notFound();
}
const tweets = await new Promise((resolve) => {
db.all(queries.Tweets.findByUserId, userId, (err, rows) => {
resolve(rows);
});
});
const userTweetList = templates.USER_TWEET_LIST_VIEW(user, tweets);
const response = templates.HTML(userTweetList);
return c.html(response);
});
app.use("/static/*", serveStatic({ root: "./" }));
serve(app);
process.stdin.on("data", (data) => {
if (data.toString().trim() === "q") {
db.close();
process.exit();
}
});
これで、/user/:id
にアクセスすると、そのユーザーが投稿したツイート一覧が表示されるようになりました。
一度database.db
を削除して、index.js
を実行してみましょう。
すると、/user/3
にアクセスすると、ぶどう三郎さんのツイート一覧が表示されますが、/user/4
にアクセスすると、404 Not Foundが表示されます。
これは最初のseed
で作ったユーザーは3人しかいないからです。
ではサイト上からユーザー登録してみましょう。
投稿後勝手にリダイレクトされて、ユーザー一覧画面が自分が登録した名前で表示されていたら成功です。
ではコミットしましょう。
git add .
git commit -m "特定のユーザーが投稿したツイート一覧を表示するようにした"
投稿フォームをスタイリングする
投稿フォームをスタイリングしていきます。
前作ったstatic/style.css
に以下のCSSを追加してください
body {
margin: 0;
background-color: #edf3f6;
}
.title {
font-size: 60px;
text-align: center;
}
.tweet-list {
display: flex;
flex-direction: column;
gap: 16px;
max-width: 768px;
padding: 16px;
box-sizing: border-box;
width: 100%;
margin: 0 auto;
}
.tweet {
display: flex;
flex-direction: column;
gap: 8px;
padding: 16px;
border-radius: 8px;
background-color: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.25);
}
form {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 20px;
width: 100%;
max-width: 480px;
margin: 0 auto;
}
label {
font-weight: bold;
margin-bottom: 5px;
text-align: left;
width: 100%;
}
input, textarea, select {
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
margin-bottom: 10px;
width: 100%;
box-sizing: border-box;
}
button[type="submit"] {
background-color: #3f8ce4;
color: white;
padding: 10px 20px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
}
button[type="submit"]:hover {
background-color: #2a6cb8;
}
これで、投稿フォームがちょっとだけ見栄えが良くなりました。
ではコミットしましょう。
git add .
git commit -m "投稿フォームをスタイリングした"
ツイートを投稿できるようにする
ツイートを投稿するためのフォームを作る
ツイートを投稿するためのフォームを作ります。
const HTML = (body) => `
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>これはただの文字列です</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
${body}
</body>
</html>
`;
const TWEET_LIST_VIEW = (tweets) => `
<h1 class="title">ツイート一覧</h1>
<div class="tweet-list">
${tweets
.map((tweet) => `<div class="tweet">${tweet.content}</div>`)
.join("\n")}
</div>
`;
const USER_REGISTER_FORM_VIEW = () => `
<h1 class="title">ユーザー登録</h1>
<form action="/user/register" method="POST">
<label for="name">名前</label>
<input type="text" name="name" id="name" />
<label for="email">メールアドレス</label>
<input type="email" name="email" id="email" />
<button type="submit">登録</button>
</form>
`;
const USER_TWEET_LIST_VIEW = (user, tweets) => `
<h1 class="title">${user.name}さんのツイート一覧</h1>
<div class="tweet-list">
${tweets
.map((tweet) => `<div class="tweet">${tweet.content}</div>`)
.join("\n")}
</div>
`;
const TWEET_FORM_VIEW = (users) => `
<h1 class="title">ツイート</h1>
<form action="/tweet" method="POST">
<label for="content">内容</label>
<textarea name="content" id="content" rows="10"></textarea>
<label for="user_id">ユーザー</label>
<select name="user_id" id="user_id">
${users
.map((user) => `<option value="${user.id}">${user.name}</option>`)
.join("\n")}
</select>
<button type="submit">投稿</button>
</form>
`;
module.exports = {
HTML,
TWEET_LIST_VIEW,
USER_REGISTER_FORM_VIEW,
USER_TWEET_LIST_VIEW,
TWEET_FORM_VIEW,
};
TWEET_FORM_VIEW
という関数を追加しました。
実際はここでユーザー認証を実装して他のユーザーが自分になりすまして投稿できないようにする必要がありますが、今回は簡単のためにユーザーを選んで投稿するようにします。
<select>
タグの中身は、users
という配列をループして、<option>
タグを作っています。
<option>
タグのvalue
属性には、user.id
を指定しています。
次に、index.js
に/tweet
にアクセスしたときの処理を追加します。
ユーザー登録と同じように、POST
メソッドでアクセスするとツイートが投稿されるようにします。
const sqlite3 = require("sqlite3").verbose();
const queries = require("./queries");
const templates = require("./template");
const { serve } = require("@hono/node-server");
const { serveStatic } = require("@hono/node-server/serve-static");
const { Hono } = require("hono");
const db = new sqlite3.Database("database.db");
db.serialize(() => {
db.run(queries.Tweets.createTable);
db.run(queries.Users.createTable);
db.run(queries.Users.create, 'りんご太郎', '[email protected]', '2022-08-15 00:00:00');
db.run(queries.Users.create, 'みかん次郎', '[email protected]', '2022-08-15 00:00:01');
db.run(queries.Users.create, 'ぶどう三郎', '[email protected]', '2022-08-15 00:00:02');
db.run(queries.Tweets.create, 'あけおめ!', 3, '2023-01-01 00:00:00');
db.run(queries.Tweets.create, '今年もよろしくお願いします!', 2, '2023-01-01 00:00:01');
db.run(queries.Tweets.create, '今年こそは痩せるぞ!', 1, '2023-01-01 00:00:02');
});
const app = new Hono();
app.get("/", async (c) => {
const tweets = await new Promise((resolve) => {
db.all(queries.Tweets.findAll, (err, rows) => {
resolve(rows);
});
});
const tweetList = templates.TWEET_LIST_VIEW(tweets);
const response = templates.HTML(tweetList);
return c.html(response);
});
app.get("/user/register", async (c) => {
const registerForm = templates.USER_REGISTER_FORM_VIEW();
const response = templates.HTML(registerForm);
return c.html(response);
});
app.post("/user/register", async (c) => {
const body = await c.req.parseBody();
const now = new Date().toISOString();
const userID = await new Promise((resolve) => {
db.run(queries.Users.create, body.name, body.email, now, (err) => {
resolve(this.lastID);
});
});
return c.redirect(`/user/${userID}`);
});
app.get("/user/:id", async (c) => {
const userId = c.req.param("id");
const user = await new Promise((resolve) => {
db.get(queries.Users.findById, userId, (err, row) => {
resolve(row);
});
});
if (!user) {
return c.notFound();
}
const tweets = await new Promise((resolve) => {
db.all(queries.Tweets.findByUserId, userId, (err, rows) => {
resolve(rows);
});
});
const userTweetList = templates.USER_TWEET_LIST_VIEW(user, tweets);
const response = templates.HTML(userTweetList);
return c.html(response);
});
app.get("/tweet", async (c) => {
const users = await new Promise((resolve) => {
db.all(queries.Users.findAll, (err, rows) => {
resolve(rows);
});
});
const tweetForm = templates.TWEET_FORM_VIEW(users);
const response = templates.HTML(tweetForm);
return c.html(response);
});
app.post("/tweet", async (c) => {
const body = await c.req.parseBody();
const now = new Date().toISOString();
await new Promise((resolve) => {
db.run(queries.Tweets.create, body.content, body.user_id, now, (err) => {
resolve();
});
});
return c.redirect("/");
});
app.use("/static/*", serveStatic({ root: "./" }));
serve(app);
process.stdin.on("data", (data) => {
if (data.toString().trim() === "q") {
db.close();
process.exit();
}
});
これで、/tweet
にアクセスすると、ツイートを投稿するためのフォームが表示されます。
そして、POST
メソッドでアクセスすると、ツイートが投稿されます。
実際に投稿してみましょう。
ではコミットしましょう。
git add .
git commit -m "ツイートを投稿するためのフォームを作った"
完成
一通りの機能が実装できたので、完成です。ここまで3回にわたる長いチュートリアルについてきてくださり、ありがとうございました。
おまけ
プログラミングをするにあたって一番勉強しやすいのは「自分で何かを作る」ということです。 「作る」に関しても、別に1から作る必要はありません。すごく大変ですし、時間もかかります。 そこで、「既存のものを少し改造してみる」という勉強方法をおすすめします。
たとえば○○をいじったらどこが変わるのか、とか、○○を追加したらどうなるのか、とか、そういうことを考えながら、既存のものを少し改造してみると、プログラミングの勉強になります。
改造のヒントを少し書いておきますね
Level1
- CSSを書いてより見栄えを良くする
- ツイート一覧にいま本文が表示されているだけなので、ユーザー名や投稿日時も表示するようにする
- ツイートの詳細画面を作る
Level2
- ユーザーのプロフィールシステム(Bioとか)を実装する
- ツイートを削除・編集できるようにする
Level3
- いいね機能を実装する
- リプライ機能を実装する
- このサイトをデプロイ(世界に公開)する
LevelMax
- ユーザー認証を実装する
- フォロー機能を実装する
- セキュリティホールを探す(実はこのチュートリアルにはセキュリティホールがあります)