TypeScript with Express
Cover Page
Back-end Page
Install dependencies
Change to your chatterd directory and install the PostgreSQL package:
server$ cd ~/reactive/chatterd
server$ pnpm install postgres
chatterd app
Edit main.ts:
server$ vi main.ts
and add the following import lines:
import postgres from 'postgres'
import type { Sql } from 'postgres'
We set up a pool of open connections to our PostgreSQL chatterdb database. Maintaining a pool of open
connections avoids the cost of opening and closing a connection on every database operation. The password used
in the call to postgres() must match the one you used when setting up Postgres earlier. The connection pool
needs to be accessible to all the URL handlers, so we store it in a global variable chatterDB:
export let chatterDB: Sql
Right inside the try{} block, add:
chatterDB = postgres('postgres://chatter:chattchatt@localhost/chatterdb')
Continuing in main.ts, find the routing table assigned to your express instance and add two routes
right after the routes for /llmprompt. These serve Chatter’s two APIs: HTTP GET request, with URL
endpoint getchatts/, and HTTP POST request, with endpoint postchatt/, invoking the get() or post()
method respectively and each given its handler function:
.get('/getchatts/', handlers.getchatts)
.post('/postchatt/', handlers.postchatt)
The functions getchatts() and postchatt() will be implemented in handlers.ts.
We’re done with main.ts. Save and exit the file.
handlers.ts
Edit handlers.ts:
server$ vi handlers.ts
First add the following imports to the top of the file:
import { randomUUID } from 'crypto'
import type { PostgresError } from 'postgres'
import { chatterDB } from './main.js'
We define a Chatt type to help postchatt() deserialize JSON
received from clients. Add these line right below the imports:
type Chatt = {
name: string
message: string
}
The handler getchatts() uses an open connection from the connection pool to query the database for stored
chatts. By default, postgres.js retrieves each database row as a JSON object, whereas we want each row
represented as an array of values only, without column name as tags. We achieve this by calling .values()
on each retrieved row. Once all the rows from the database are retrieved, if no exception has been raised,
we insert the resulting array of chatts into the response JSON object. Add getchatts() to your
handlers.ts after the definition of the llmprompt() function.
export async function getchatts(req: Request, res: Response) {
try {
const chatts = await chatterDB`SELECT name, message, id, time FROM chatts ORDER BY time ASC`.values()
res.json(chatts)
} catch (error) {
logServerErr(res, `${error as PostgresError}`)
}
}
Similarly, postchatt() receives a posted chatt in the expected JSON format, deserializes it into the
Chatt type, and inserts it into the database, using a connection from the pool. The UUID and time stamp
of each chatt are generated at insertion time. Add postchatt() to the end of your handlers.ts:
export async function postchatt(req: Request, res: Response) {
let chatt: Chatt = req.body
try {
await chatterDB`INSERT INTO chatts (name, message, id) VALUES (${chatt.name}, ${chatt.message}, ${randomUUID()})`
res.json({})
} catch (error) {
logClientErr(res, HttpStatus.BAD_REQUEST, `${error as PostgresError}`)
}
}
We’re done with handlers.ts. Save and exit the file.
Build and Test run
![]()
TypeScript is a compiled language, like C/C++ and unlike JavaScript and Python, which
are an interpreted languages. This means you must run npx tsgo each and every time you made
changes to your code, for the changes to show up when you run node.
To build your server, transpile TypeScript into JavaScript:
server$ npx tsgo
To run your server:
server$ sudo node main.js
# Hit ^C to end the test
You can test your implementation following the instructions in the
Testing Chatter APIs section.
References
| Prepared by Sugih Jamin | Last updated January 10th, 2026 |