React, Express, Node, Postgres, Typescript, TypeORM
This boilerplate is intended to allow for the quick setup of an application skeleton leveraging best practices, but also some opinionated libary and technology selections based on my preferred tools. However, outside of the major technologies being leveraged, it should be possible to easily remove or substitute many of the technologies (eg. replacing PostegreSQL with MongoDB, Express with Koa, or TailwindCSS in favor of SASS).
Live Demo of this repository hosted on Heroku.
Note: The demo is hosted on a free Heroku dyno, so it will likely require a few seconds to warm up
Clone this git repository: git clone https://github.com/trm313/react-typeorm-ts-postgres.git [directory]
Install the server packages: npm install
Install the client packages: cd client && npm install
Create a .env
file at the server root, and add keys as defined in the ./src/config/env.ts
file
PG_URL
is your PostegreSQL connection string in the form ofpostgresql://[username]:[password]@[host]:[port]]/[database]
Example:
postgresql://myuser:pass123@localhost:5432/mydatabase
The
FIREBASE_*
parameters are gathered from your Firebase projectService accounts
setting
client/src/services/firebase.js
fileIn two terminals execute the following commands from the application root:
npm run dev
- starts the server in watch mode on port 3001
npm run client
- starts the client in development & watch mode on port 3000
In development mode, calls from the frontend that are prefaced with /api
will be proxied to the development server on port 3001, as configured in the setupProxy.js
file
Executing npm run heroku-postbuild
will trigger both the backend server and the React frontend client to build. The application will then be served statically from the server root.
You can execute this command locally to test functionality of a static deployment, and it will also be key to deploying your application to Heroku as described below
To deploy this application to Heroku:
Resources
tabSettings
tab. Important: Without the PG_URL
variable, the application will not startmaster
branchWhen deployed, Heroku will install the dependencies configured in the application’s root package.json
file, and then execute the heroku-postbuild
script. Once finished, Heroku will run the command specified in the Procfile
at the application’s root, which for us is npm start
.
If present, Heroku will run the heroku-postbuild
script instead of the build
script. The heroku-postbuild
script in the root package.json
file will trigger a full build of the application, including:
dist/
folderclient/build
folderFor more granular details, see the specific breakdowns below
The build
script from the server root will trigger tsc
which will compile the source into the dist/
folder. This location is specified in the tsconfig.json
file via the outDir
key.
Running npm start
from the server root will then execute node dist/server.js
, spinning up the server from the compiled dist/
folder.
The server will be looking to serve the front-end application from the client/build
folder, which is compiled in the Frontend build step. The Frontend build is triggered by the heroku-postbuild
script, which is automatically triggered when publishing to Heroku.
The heroku-postbuild
script triggers the frontend to build its dependencies, and then launches the build
script from the client package.json
file. This build
script launches two additional scripts: build:css
and build:react
.
build:css
tells TailwindCSS to compile from the src/styles/index.css
file into an output file src/index.css
, which is referenced directly from the index.js
frontend entry point.
build:react
triggers the standard Create-React-App build process, which builds the client into subfolder ./Build
, which is served from the backend server directly.
User authentication is handled via Firebase. On the front-end, this repo leverages the drop-in Firebase UI component to handle all authentication forms and providers. Authentication is persisted in local storage, and automatically validated on page load.
During this authentication validation, the user’s information and access_token
are collected, and stored in the Redux user
state.
This access_token
is then supplied in the request header during calls to protected routes on the backend, where the token is validated with Firebase inside a router middleware, to then facilitate all data fetching calls.
Firebase is initialized on the frontend in the client/services/firebase.js
file, and exports various utilities, that can be called as needed.
Inside the App
component is a useEffect
call that will launch a function to listen for updates to Firebase authentication, listenToFirebaseAuth
. This will trigger on page load (when the user’s stored data is validated), when a user logs in, and when a user logs out.
Callbacks from this function will invoke Redux Actions signUserIn
and signUserOut
as appropriate, which will update the userReducer
state.
There is a /logout
route configured that will also invoke Firebase’s auth.signOut()
function (thereby triggering the signUserOut
action), as well as automatically redirecting the user back to the /
route.
Protected routes can be configured, and there is an example set up in the App
component. Since it can take a couple of seconds to validate a returning user (who might refresh the page on a protected route), the PrivateRoute
component will render a LoadingScreen
component if that validation process is in progress. This prevents the user from being redirected back to the fall-back route during this gap.
The user’s access_token
is to be included in the request header as FIREBASE_AUTH_TOKEN
. Here is how one can do this in the module axios
:
import axios from "axios";
axios.defaults.headers.common["FIREBASE_AUTH_TOKEN"] = user.data.accessToken;
const result = await axios.get("/api/v1/user");
The verifyAuth
function in the auth.ts
middleware can be applied to any route handlers to ensure the user making the API call is authenticated, and then provide them with the data corresponding to their account.
This middleware will decode the provided token against Firebase to re-reveal their user details. The user’s uuid
and email
are then attached to the request as req.user
before passing into the next middleware.
Server skeleton, middlewares, routes, unit tests, integration tests: