Demo: https://vogler-track-time.herokuapp.com
This is a test project building some todo/time-tracking app using:
- Snowpack as a fast build tool (instead of webpack, Parcel)
- React for a reactive UI
- Recoil
- to avoid re-render of non-running timers (value-dependent dependency via
selectorFamily
) - to avoid re-mount of
InputForm
while adding a new item (decouple state from component tree)
- to avoid re-render of non-running timers (value-dependent dependency via
- react-router for routing in the browser (routes are redirected to / by the server)
- Recoil
- chakra-ui as UI component library
- react-icons
- maybe try Formik for forms
- Prisma as ORM (instead of TypeORM which I had some issues [1] [2] with in https://github.com/vogler/syncmine)
- PostgreSQL (or sqlite etc.) as database
- missing some values/types, no trees, see notes below
- grant for OAuth with google; requires little code, dynamic json config. Discarded alternatives:
- auth0 free plan has 7k users and 2 social connections; their React example uses hook, so page first loads, then checks auth and then loads rest -> too many round trips (same as with firebase)
- passport has passport-local for username+password and normalized profile information, but requires way too much code
Tried Svelte and Firebase in an older iteration: https://github.com/vogler/tasktime_svelte-firebase. By now Svelte seems to officially support TypeScript: https://svelte.dev/blog/svelte-and-typescript.
Run npm install
.
If you want to use sqlite instead of PostgreSQL, edit backend/schema.prisma
.
- setup DB:
brew install postgresql && brew services start postgresql && createdb tasktime
- set
DATABASE_URL
, e.g. create.env
file withDATABASE_URL = 'postgresql://user@localhost:5432/tasktime'
- setup tables:
npm run db-push
Use npm start
to start the server with reload on changes and HMR via snowpack.
For production see heroku.com example in Procfile
.
Maybe check out Next.js for easier SSR; example: https://github.com/prisma/prisma-examples/tree/latest/typescript/rest-nextjs-api-routes
Seems strange that there is no framework/library that only requires the database schema and automatically provides an API on the server for it.
Also, no one seems to care about duplication. GraphQL just introduces more boilerplate for little benefit compared to just calling database functions on the client (can also select subset of fields to save data; authentication/authorization can be a middleware on the server, just need some access annotations for the schema).
Use row-level security in PostgreSQL for authorization and https://jwt.io to authenticate API requests as in PostgREST's auth? Would need to CREATE ROLE
for every user?
Was not happy with
- https://github.com/redwoodjs/redwood - only serverless, but need to manually setup a database server...
- https://github.com/blitz-js/blitz - looks better, but just generates the boilerplate on the server instead of avoiding it
- https://github.com/layrjs/layr - just MongoDB, too much boilerplate in models
- https://www.prisma.io/docs/concepts/overview/prisma-in-your-stack/graphql - list of Prisma & GraphQL examples, all seem not DRY
- https://github.com/graphile/postgraphile - runs a GraphQL server that watches a PostgreSQL database; schema not as code, no generated code, no autocomplete? migrations?
- https://github.com/hasura/graphql-engine - GraphQL from PostgreSQL, realtime queries, too much boilerplate; haskell
- https://github.com/PostgREST/postgrest - REST API server from existing PostgreSQL database; haskell, no good client-side lib in Typescript, postgrester just uses SQL strings
Based on the generated code from Prisma, we define a generic server endpoint /db/:model/:action
and a generic db
object on the client that has Prisma's types but just relays the call to the server.
- Would be nice to have values for the generated types to define custom functions: prisma/prisma#5291
- Use the following? https://github.com/valentinpalkovic/prisma-json-schema-generator
- No good way to get the union of several models/tables. See comments in
History.tsx
: we currently fetch all entries and then merge sort them on the client.- Support for a Union type #2505
- Option brand the model name into data #5315
- implemented basic
db_union
with raw query, but is still missinginclude
which we use, summary: prisma/prisma#2505 (comment)
- Does not support trees yet (TypeORM does):
- Tree structures support #4562
- Support recursive relationships #3725
- recursive query (with queryRaw) to get transitive hull of first select:
with recursive subtodos as (select * from "Todo" where id=64 union select p.* from "Todo" p inner join subtodos s on s.id = p."parentId") select * from subtodos;`
- recursive query (with queryRaw) to get transitive hull of first select:
- raw query, native PostgreSQL type:
FP/types/meta:
- https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-func.html
- https://github.com/gcanti/fp-ts
- https://github.com/gcanti/io-ts useful for Prisma abstraction? prob. would have to be used in the generated code.
- https://github.com/gcanti/monocle-ts Lens, Prism, Traversal...
- https://github.com/pfgray/ts-adt
- https://github.com/kimamula/ts-transformer-keys via https://github.com/cevek/ttypescript for runtime type information
- https://github.com/sindresorhus/type-fest
- https://github.com/piotrwitek/utility-types
Before adding auth, Chrome Dev Tools' network tab said ~300ms/350ms for DOM/Load in incognito mode with npm start
(snowpack dev), and ~600ms in normal mode.
Noticed that the longer time was due to extensions (Surfingkeys, React dev tools, others?) -> only measure in incognito mode.
After adding auth, not logged in, at 350ms/360ms. Can see in waterfall that it's due to pending websocket request to hmr-client.js -- strange that affects load time despite staying open forever.
With npm run start-prod
at 200ms/206ms (no bundler yet) -> no more hmr-client.
Seems like favicon.ico (and manifest.webmanifest only when not in incognito) are loaded after load is done. However, last request is started at 120ms and done in 8ms, but then there's nothing in the waterfall and result is 208ms/213ms -> not changed by removing favicon.ico/manifest.webmanifest.
https://web.dev/measure for https://vogler-track-time.herokuapp.com performance 40, accessibility 85, best practices 100, SEO 100.
First Contentful Paint 5.2 s, Time to Interactive 8.8 s, Speed Index 10.1 s
Estimated Savings: Enable text compression: 4.2 s (@chakra-ui/react.js 555.9 KB / 731.4 KB), Remove unused JavaScript 3.6 s, Use HTTP/2 2.91 s, Minify JavaScript 1.95 s
Load time >= with compression. Probably because it re-compresses every time. With shrink-ray 791 kB -> 740 kB transferred (both 460 kB with cache). Did not compile on heroku when installing via ssh, but then worked on automatic deploy. Encoding is br for bigger files.
Bundlers: bundle, but don't minify for now since we replace variables on the server. Values from dev tools with 'disable cache'. First row is not logged in, second is logged in.
bundle | transferred | resources | finish | DOMContentLoaded | Load |
---|---|---|---|---|---|
none dev |
861 kB 940 kB |
4.1 MB 4.7 MB |
190 ms 1.60 s |
351 ms 1.25 s |
366 ms 1.61 s |
none build |
250 kB 287 kB |
1.1 MB 1.3 MB |
126 ms 327 ms |
201 ms 275 ms |
206 ms 334 ms |
esbuild es2018 |
208 kB 213 kB |
1.1 MB 1.1 MB |
59 ms 279 ms |
183 ms 258 ms |
196 ms 366 ms |
webpack |
https://web.dev/measure with esbuild es2018:
First Contentful Paint 2.2 s, Time to Interactive 2.7 s, Speed Index 4.2 s
Estimated Savings: Remove unused JavaScript: 0.75 s, Reduce initial server response time: 0.7 s, Minify JavaScript: 0.3 s
✨ Bootstrapped with Create Snowpack App (CSA).
npx create-snowpack-app tasktime_snowpack-react-chakra-prisma --template @snowpack/app-template-react-typescript
Runs the app in the development mode. Open http://localhost:8080 to view it in the browser.
The page will reload if you make edits. You will also see any lint errors in the console.
Builds a static copy of your site to the build/
folder.
Your app is ready to be deployed!
For the best production performance: Add a build bundler plugin like "@snowpack/plugin-webpack" to your snowpack.config.js
config file.