Skip to content

Latest commit

 

History

History
109 lines (79 loc) · 7.37 KB

README.md

File metadata and controls

109 lines (79 loc) · 7.37 KB

Fastify + HTMX + TypeScript starter kit

This starter kit aims to offer an efficient approach for initiating projects with both backend and frontend logic. It's designed to utilize a modern tech stack while maintaining minimal dependencies for simplicity and effectiveness.

This project renders all the HTML on the server (SSR), using JSX as the template language and HTMX to (progressively) enhance the frontend interactions. Let me clarify it once more: JSX is only used on the server, not on the client. The client is NOT a React application.

A typical use case could be a single web page that fetches some information from a third-party API, but you also want to have a small backend where to keep your API keys and maybe a cache to avoid hitting a rate limit on those external APIs.

The kit contains:

  • A server application based on Fastify
  • A client application (no frameworks, just TypeScript)
  • A demo micro application using HTMX: reads the current server time and displays it in the browser, auto-refreshing every 30s (or manually)
  • Server side error handling (500s and 404s)
  • TypeScript linting (via eslint and some plugins)
  • JSX rendering using just TS and Preact

The kit does NOT contain:

  • a JavaScript or CSS bundler
  • Tests (but read at the end for some suggestions)
  • Deployment instructions
  • Sophisticated cache busting strategies for the client
  • ts-node because we can do without it

Probably worth mentioning that I have also created a (now deprecated) similar starter kit using Express and EJS (not JSX) if you prefer that server. I would suggest to not use it.

At the time of writing, Windows has not been tested as a dev environment.

Install

npm create fastify-htmx-ts-app@latest my-app
# or
npx create-fastify-htmx-ts-app@latest my-app

After that, you have to install dependencies using your favourite package manager (i.e. npm) and run some commands:

You also have npm lint, npm build and of course npm start (for production).

Note that the project uses the (experimental) nodejs' native watch feature to monitor file changes and restart the server (in dev mode only). We don't use nodemon but if you cannot use a fairly new version of nodejs (19+) you might need it. To use nodemon, just replace node --watch-path with nodemon -w.

Configuration (.env)

If you need to use a .env configuration file (not included), we suggest using nodejs >= 20.6, to be able to read a .env file without using an additional dependency (usually dotenv). If you cannot use node 20+, then just install and use dotenv.

Tech stack

  • Fastify
  • JSX templates
  • TypeScript everywhere
  • HTMX for the frontend to speak to the backend
  • Chota framework for the CSS, because it's small and cute
  • Helmet for security
  • Static for serving static assets

Additional tools provided

  • concurrently
  • prettier and a bare-bone config
  • client's and server's own tsconfig which extends a base one

This project uses ESLint and Prettier: don't forget to install/enable their extension in Visual Studio Code.

Bonus

  • There is an example of how to write a HTMX extension
  • Configurations are all in the package.json to remove clutter

Project structure

  • server/app.ts contains just the bare-bone startup code for the server
  • server/lib contains any additional server module:
    • bootstrap.ts is the module setting up Fastify and its plugins
    • jsxRender is what makes it possible to use JSX with Fastify, as explained in the original post
    • router.tsx contains the routes definition and the error handling
    • assets.ts is used to generate a cache proof name for the assets
    • There are also some constants.ts
  • server/views contains the view templates in JSX format, mostly the Pages and the Components.
  • client/app.ts contains just some code that it's run when the page is loaded in the browser
  • client/lib/tools.ts is just used as an example of natively including a js module at runtime
  • assets contains js, css, and vendor files. Keep in mind that the js assets are symlinked from the dist directory. All the assets are mounted under the /a virtual folder

Since this project doesn't use ts-node, your app is run directly from the dist folder (check the script in package.json to understand how).

Deployment

This is on you. I own a small VPS and I run all my projects from there. I to run the server process behind a reverse proxy where I also end the TLS connection. I use ngnix and letsencrypt for my tls certificates. I find the guides from DigitalOcean extremely valuable for this kind of setup:

If you have successfully deployed a project inherited from this kit, in some cloud, and you want to share the steps please open a PR!

What is missing

  • There is no cookie support, because we hate cookie banners
  • Assets are not tgz compressed because this should be the job of your reverse proxy
  • Both server and client code reside in the same git repository, and they both share the same package.json and node_modules and that's OK for relatively small project, but you can also decide to move to a monorepo layout using something like nx or TurboRepo for managing it
  • Only the main client js module (app.js) is provided with a cache-bust parameter, whereas any imported module (as in tools.js in this project) is not, which means that they only rely to the cache settings of the static middleware. I will leave it up to you to decide if that's enough or if you want to add an additional build step to handle such cases

Where are the tests!?

A testing system is also not installed by default, but if you are like me and love Vitest, just follow these instructions (for the client, but it should work for the server as well):

  • npm install vitest --save-dev
  • Edit client/tsconfig.json and add an exclude option for your tests: this is because vitest doesn't use tsc for the typescript sources and you should tell tsc to not compile and build the tests
  • Add a test script in your package.json with something like vitest ./client/**/*.spec.ts
  • Since we are excluding tests from the TS projects, eslint won't be able to find them anymore and we need to put them back somewhere. To do so, you can follow these instructions. I would add a tsconfig.eslint.json which extends our base, doesn't emit and includes the test files
  • Write your tests away!