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

:point_right: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