Repository navigation
Expand file tree
/
Copy pathserver.js
More file actions
94 lines (80 loc) · 2.9 KB
/
Copy pathserver.js
File metadata and controls
94 lines (80 loc) · 2.9 KB
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import express from "express";
import pg from "pg";
// Light Cloud tells the app which port to listen on through PORT.
// 8080 is the fallback for running it on your own machine.
const PORT = Number(process.env.PORT) || 8080;
// DATABASE_URL comes from Light Cloud. It must end with
// ?sslmode=require&uselibpqcompat=true for the node-postgres driver.
// max: 3 keeps the pool small: every running instance opens its own.
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL, max: 3 });
// One JSON object per line. The Logs tab reads "severity" for the level.
function log(severity, message, fields = {}) {
console.log(JSON.stringify({ severity, message, ...fields }));
}
const app = express();
app.use(express.json());
app.use((req, res, next) => {
const started = Date.now();
res.on("finish", () => {
const severity = res.statusCode >= 500 ? "ERROR" : res.statusCode >= 400 ? "WARNING" : "INFO";
log(severity, "request", { method: req.method, path: req.path, status: res.statusCode, ms: Date.now() - started });
});
next();
});
app.get("/", (req, res) => {
res.json({ message: "Notes API", routes: ["GET /notes", "POST /notes", "GET /health"] });
});
app.get("/health", async (req, res, next) => {
try {
await pool.query("SELECT 1");
res.json({ status: "ok", database: "ok" });
} catch (err) {
next(err);
}
});
app.get("/notes", async (req, res, next) => {
try {
const { rows } = await pool.query("SELECT id, text, created_at FROM notes ORDER BY id DESC LIMIT 50");
res.json(rows);
} catch (err) {
next(err);
}
});
app.post("/notes", async (req, res, next) => {
const text = typeof req.body?.text === "string" ? req.body.text.trim() : "";
if (!text || text.length > 500) {
return res.status(400).json({ error: "Send {\"text\": \"...\"} with 1 to 500 characters." });
}
try {
const { rows } = await pool.query(
"INSERT INTO notes (text) VALUES ($1) RETURNING id, text, created_at",
[text]
);
res.status(201).json(rows[0]);
} catch (err) {
next(err);
}
});
app.use((req, res) => {
res.status(404).json({ error: "Not found" });
});
app.use((err, req, res, next) => {
log("ERROR", err.message, { path: req.path });
res.status(500).json({ error: "Something went wrong" });
});
// Create the table on start. A real project would use migrations.
await pool.query(`
CREATE TABLE IF NOT EXISTS notes (
id SERIAL PRIMARY KEY,
text TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
)`).catch((err) => log("ERROR", `database not ready: ${err.message}`));
const server = app.listen(PORT, () => {
log("INFO", `listening on port ${PORT}`);
});
// Light Cloud stops old instances with SIGTERM after a new version is live.
// Finish the requests in flight, then exit.
process.on("SIGTERM", () => {
log("INFO", "SIGTERM received, shutting down");
server.close(() => pool.end().then(() => process.exit(0)));
});