Skip to content
/ pht_stack Public template

P stands for Postgres, H stands for HTMX and T stands for Typescript. Fastify is hidden but is there. This is a stack for developing fast web applications.

Notifications You must be signed in to change notification settings

JacopoPatroclo/pht_stack

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

logo

PHT Stack

P stands for Postgres, H stands for HTMX, and T stands for Typescript. Fastify is hidden but is there. This is a stack for developing fast web applications. You can write your template with JSX to have a composable and type-safe way to write your views.

Why

If you want to develop a server-side first application with Node.js you have a lot of options today. But I feel that the server-first approach in this world of spa vs rsc is a little bit "put aside". This has led to, in my opinion, poor design choices in designing those frameworks. Let's see some of the concerns with the current state of things and how this stack tries to solve them.

Template engines

We still don't have a well-integrated template engine with Typescript. We have some options like EJS, Pug, and others, but they are not type-safe and the composition apis that they offer is sometimes cumbersome and prone to errors. Fortunately, we have JSX, a well-known and type-safe way to write views. We choose to use @kitajs/html as a JSX library because it is fast and handles suspense and error boundaries.

Request validation

If we want to go with something like Astro or Next.js, the HTTP server exposed by both is not going to help you with request validation, serialization, HTTP verb segregation (if you want to have a POST /path and a GET /path you have to use if statements) and other things that are common in a server-side application. We choose Fastify because it is fast, handles serialization and request validation out of the box, and has a good plugin system. The only downside to Fastify is the fact that there is no good starter template for developing server-first web applications. This repository is trying to solve this problem.

Who needs file routing?

I don't like the file routing system that Next.js and other frameworks use. I think that it is a little bit cumbersome to use and it is really hard to do it in a typesafe way. Morehover I don't want to have to name my files following some weird convention. When you want to search for a file that handles the routing part of your product detail page you are going to search for something like product or detail, you are not going to search for [...slug].tsx.

Database

I think that Drizzle in combination with Drizzle-kit is the best ORM currently available for Typescript. I have nothing more to say.

Getting started

You can clone this repo and run the following

rm -rf .git
git init
pnpm install
pnpm gen-session-key
pnpm dev

and you should have a running server at http://localhost:3000 serving some HTML.

Project Structure

  • src contains the source code for the server
    • components contains the components that are used globally
    • database contains the database connection and the drizzle tables definitions
    • plugins contains the plugins that are used globally (registered automatically)
    • pages contains the pages that will contain all the business logic of your application. Every file in this directory that ends with .routes.tsx or .routes.ts will be registered automatically. Keep in mind that the loader that has the responsibility to load these files is the pages.loader.tsx file. As you can see there are an error and 404 handlers. Those are registered because the pages are supposed to always return HTML.
    • api contains the API routes that are exposed by the application. Every file in this directory that ends with .routes.tsx or .routes.ts will be registered automatically. Keep in mind that the loader that has the responsibility to load these files is the api.loader.tsx so if you want to declare custom handlers logic only for APIs you can do it there. Remember, APIs are supposed to always return JSON.
    • env.ts validate and expose environment variables
    • app.ts is the file that creates the server (see builder pattern in Fastify documentation)
    • index.ts is the entry point of the server
    • main.css is the main CSS file used by Tailwind CSS
  • client contains the client code, and each TypeScript file in this directory will be used as an entry point for the client scripts. The output will be placed in the public/dist directory.
  • public contains the public files that are served by the server
  • migrations contains the database migrations generated by Drizzle
  • tsconfig.app.json is the TypeScript configuration file for the server
  • tsconfig.client.json is the TypeScript configuration file for the client
  • tsconfig.json is the base typescript configuration file
  • tsconfig.test.json is the TypeScript configuration file for transpiling for the tests
  • drizzle.cofig.ts the configuration file for Drizzle
  • cypress.config.ts the configuration file for Cypress
  • cypress contains the end-to-end tests
    • e2w contains the end-to-end tests files
    • support contains the support files for the end-to-end tests, like commands and custom assertions
    • fixtures contains the fixtures used by the end-to-end tests

Technologies

This project uses the following technologies:

Make sure to check the documentation of each technology to understand how to use them.

Development

This project uses pnpm as the package manager. To install the dependencies, run pnpm install. To start the development server, run

pnpm dev

To run the unit tests, run

pnpm test

To build for production, run

pnpm build

To run the type check and lint, run

pnpm typecheck
pnpm lint

There is a check command to run various check commands at once. See package.json for more details.

pnpm check

To run the end-to-end tests, run

pnpm e2e

Deployment

This section varies depending on the deployment platform. There is a Dockerfile that you can use to create a container. The container will serve the application on port 3000. You can build it like this:

docker build -t pht-stack .

After that, you can push that container to a container registry and deploy it to your platform of choice.

E2E Tests

This project uses Cypress for end-to-end tests. Locally, while developing your app you should start the dev serve into one terminal and run the Cypress GUI in another terminal. To do that, run:

pnpm dev
pnpm cypress open

This will open the Cypress GUI where you can run the tests. You can also run the tests in headless mode by running:

pnpm e2e

Use Alpine.js

You can easily use Alpine.js in your project. Just install it with

pnpm install alpinejs

Then you can edit like so the client/main.ts file

import Alpine from 'alpinejs';
import htmx from 'htmx.org';

window.htmx = htmx;
window.Alpine = Alpine;

Alpine.start();

declare global {
  interface Window {
    htmx: typeof htmx;
    Alpine: typeof Alpine;
  }
}

// ...rest of the file

Use another database

To use another database, you need to go into the package.json and remove the current driver dependencies, installing the one that you want to use with Drizzle. Afterward, you need to update the database.ts file with the new way of creating the SQL client (changing the content of the function makeSqlClient). The last thing you need to do is to update the Drizzle config file with the new driver and connection parameters if needed.

Use Preact or other JSX libraries for the frontend

You can configure the client to use Preact or other jsx libraries by changing the jsx, jsxFactory, and jsxFragmentFactory in the tsconfig.client.json file. Then you need to install the library that you want to use, for example

pnpm install preact

After that, you just create a new entry point file inside the client directory and you are good to go. You can now in your HTML import the script from public/dist. Remember to add also an element where the app will be mounted.

Contributing

If you like to change some default behavior or setup for this starter, feel free to open an issue or a pull request. I will be happy to help you. Make sure that you add some kind of explanation about the changes that you are proposing. So that we can have a meaningful discussion about it.

About

P stands for Postgres, H stands for HTMX and T stands for Typescript. Fastify is hidden but is there. This is a stack for developing fast web applications.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published