### Instruction:
Build a JavaScript/TypeScript Express.js RESTful API that supports CRUD operations for managing a collection of books, including their titles, authors, and publication dates. Use PostgreSQL as the backend database.
PLAINFORMAT

### Response:

```typescript
// src/index.ts
import * as express from "express";
import * as bodyParser from "body-parser";
import { booksRouter } from "./routes/books";

const app = express();
app.use(bodyParser.json());
app.use("/books", booksRouter);

const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});

// src/db.ts
import * as pgPromise from "pg-promise";
import * as dotenv from "dotenv";

dotenv.config();

const pgp = pgPromise();
const db = pgp({
  host: process.env.DB_HOST,
  port: Number(process.env.DB_PORT),
  database: process.env.DB_NAME,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
});

export default db;

// src/routes/books.ts
import { Router, Request, Response } from "express";
import db from "../db";

export const booksRouter = Router();

// Get all books
booksRouter.get("/", async (req: Request, res: Response) => {
  try {
    const books = await db.any("SELECT * FROM books");
    res.status(200).json(books);
  } catch (error) {
    res.status(500).json({ error });
  }
});

// Get book by ID
booksRouter.get("/:id", async (req: Request, res: Response) => {
  const id = parseInt(req.params.id);

  try {
    const book = await db.oneOrNone("SELECT * FROM books WHERE id = $1", [id]);
    if (book) {
      res.status(200).json(book);
    } else {
      res.status(404).json({ message: "Book not found" });
    }
  } catch (error) {
    res.status(500).json({ error });
  }
});

// Create a new book
booksRouter.post("/", async (req: Request, res: Response) => {
  const { title, author, publication_date } = req.body;

  try {
    const result = await db.one(
      "INSERT INTO books(title, author, publication_date) VALUES($1, $2, $3) RETURNING *",
      [title, author, publication_date]
    );
    res.status(201).json(result);
  } catch (error) {
    res.status(500).json({ error });
  }
});

// Update an existing book
booksRouter.put("/:id", async (req: Request, res: Response) => {
  const id = parseInt(req.params.id);
  const { title, author, publication_date } = req.body;

  try {
    const result = await db.result(
      "UPDATE books SET title = $1, author = $2, publication_date = $3 WHERE id = $4",
      [title, author, publication_date, id]
    );

    if (result.rowCount > 0) {
      res.status(200).json({ message: "Book updated" });
    } else {
      res.status(404).json({ message: "Book not found" });
    }
  } catch (error) {
    res.status(500).json({ error });
  }
});

// Delete a book
booksRouter.delete("/:id", async (req: Request, res: Response) => {
  const id = parseInt(req.params.id);

  try {
    const result = await db.result("DELETE FROM books WHERE id = $1", [id]);

    if (result.rowCount > 0) {
      res.status(200).json({ message: "Book deleted" });
    } else {
      res.status(404).json({ message: "Book not found" });
    }
  } catch (error) {
    res.status(500).json({ error });
  }
});
```