{"id":31096632,"url":"https://github.com/peter-haro/node-express-sequelize-nextjs-realworld-example-app","last_synced_at":"2026-04-07T07:45:48.911Z","repository":{"id":313842957,"uuid":"1053118801","full_name":"Peter-Haro/node-express-sequelize-nextjs-realworld-example-app","owner":"Peter-Haro","description":"Node.js + Express.js + Sequelize + SQLite/PostgreSQL + Next.js fullstack static/SSG/ISG Example Realworld App. Includes Heroku deployment, DB migration, DB and API unit testing, DB seeding, and ESLint linting setups.","archived":false,"fork":false,"pushed_at":"2025-09-09T03:11:53.000Z","size":965,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"deploy","last_synced_at":"2025-09-09T05:02:07.145Z","etag":null,"topics":["expressjs","fullstack-development","next-js","node-js","postgresql","real-world-problem-solving","sequalizejs","sqlite"],"latest_commit_sha":null,"homepage":"","language":"CSS","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Peter-Haro.png","metadata":{"files":{"readme":"README.adoc","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-09T02:43:25.000Z","updated_at":"2025-09-09T03:11:56.000Z","dependencies_parsed_at":"2025-09-09T05:02:14.483Z","dependency_job_id":"0b8c1011-95a8-489d-ac99-43f309c37b6a","html_url":"https://github.com/Peter-Haro/node-express-sequelize-nextjs-realworld-example-app","commit_stats":null,"previous_names":["peter-haro/node-express-sequelize-nextjs-realworld-example-app"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/Peter-Haro/node-express-sequelize-nextjs-realworld-example-app","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Peter-Haro%2Fnode-express-sequelize-nextjs-realworld-example-app","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Peter-Haro%2Fnode-express-sequelize-nextjs-realworld-example-app/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Peter-Haro%2Fnode-express-sequelize-nextjs-realworld-example-app/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Peter-Haro%2Fnode-express-sequelize-nextjs-realworld-example-app/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Peter-Haro","download_url":"https://codeload.github.com/Peter-Haro/node-express-sequelize-nextjs-realworld-example-app/tar.gz/refs/heads/deploy","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Peter-Haro%2Fnode-express-sequelize-nextjs-realworld-example-app/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":275465741,"owners_count":25469885,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-16T02:00:10.229Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["expressjs","fullstack-development","next-js","node-js","postgresql","real-world-problem-solving","sequalizejs","sqlite"],"created_at":"2025-09-16T18:47:02.822Z","updated_at":"2025-09-16T18:47:07.501Z","avatar_url":"https://github.com/Peter-Haro.png","language":"CSS","readme":"= Node.js + Express.js + Sequelize + SQLite/PostgreSQL + Next.js fullstack static/SSR/SSG/ISG Example Realworld App\n:idprefix:\n:idseparator: -\n:sectanchors:\n:sectlinks:\n:sectnumlevels: 6\n:sectnums:\n:toc: macro\n:toclevels: 6\n:toc-title:\n\n\nIncludes \u003c\u003cdatabase-migrations\u003e\u003e, \u003c\u003ctest-js,DB and API unit testing\u003e\u003e, \u003c\u003cgenerate-demo-data,DB seeding\u003e\u003e, and \u003c\u003clinting\u003e\u003e setups.\n\nThis is a branch of https://github.com/Peter-Haro/node-express-sequelize-realworld-example-app rebased on top of the `master` branch of that repository, we show it in a separate GitHub repository just to give it more visibility.\n\n\nThis Full Stack app is part of the [RealWorld](https://github.com/gothinkster/realworld) project.\n\nYou might also check my [Laravel version](https://github.com/Peter-Haro/laravel-real-example) of this app.\nYou might also check my [Ruby on Rails version](https://github.com/Peter-Haro/ruby-on-rails-realworld-example-app) of this app.\n\n\nIdeally we would like to have both demos on the same tree as they share all backend code, but Next.js and NPM impose too many restrictions which we don't have the time to work around.\n\nThe frontend part of this repository is a fork of https://github.com/reck1ess/next-realworld-example-app/tree/66003772a42bf71c7b2771119121d4c9cf4b07d4 which was SSR only via API, not SSG/ISG.\n\nWe decided to merge it in-tree with the API rather than keep it a separate submodule because the fullstack implementation means that the barrier between front-end and back-end becomes is quite blurred, e.g. \"front-end\" files under `/pages` also contain functions with direct backend database access without going through the API.\n\nBoth Next.js and the backend API run on the same Express.js server via a https://nextjs.org/docs/advanced-features/custom-server[Next.js custom server].\n\nUser-specific information such as \"do I follow this article or not\" is fetched by the client after it receives the user-agnostic statically generated page. The client then patches that page with this new user-specific information. See also: \u003c\u003cwhat-works-with-javascript-turned-off\u003e\u003e.\n\nThe design goals of this repository are similar to https://dev.to/givehug/next-js-apollo-client-and-server-on-a-single-express-app-55l6 except that we are trying to implement the Realworld App :-) GraphQL would be ideal, but its need is greatly diminished by SSR, which essentially forces manual ad-hoc implementation of GraphQL.\n\nThis repository is a baseline for the more realworld/advanced/specialized: https://github.com/cirosantilli/cirodown/tree/master/web We are trying to port back any problems that are noticed in that repository here. Some features will only be present there because of our constraint of not diverging from the Realworld spec.\n\ntoc::[]\n\n== Local development with SQLite\n\n....\nnpm install\nnpm run dev\n....\n\nYou can now visit http://localhost:3000[] to view the website. Both API and pages are served from that single server.\n\nYou might also want to generate some test data as mentioned at: \u003c\u003cgenerate-demo-data\u003e\u003e.\n\nThe SQLite database is located at `db.sqlite3`.\n\nThe Node.js and NPM versions this was tested with are specified at link:package.json[] `engines:` entry, which is what the \u003c\u003cheroku-deployment\u003e\u003e uses https://devcenter.heroku.com/articles/nodejs-support#specifying-a-node-js-version[as mentioned here]. You should of course try to use those exact versions for reproducibility. The best way to install a custom node and NPM version is to use NVM as mentioned here: https://stackoverflow.com/questions/16898001/how-to-install-a-specific-version-of-node-on-ubuntu/47376491#47376491[].\n\n== Local optimized frontend\n\n....\nnpm install\nnpm run build-dev\nnpm run start-dev\n....\n\nIf you make any changes to the code, at least code under `/pages` for sure, you have to rebuild before they take effect in this mode, as Next.js appears to also run server-only code such as `getStaticPaths` from one of the webpack bundles.\n\nRunning `next build` is one very important test of the code, as it builds many of the most important pages of the website, and runs checks such as TypeScript type checking. You should basically run it after every single commit that touches the frontend.\n\nIf you look into what `npm run start-dev` does in the `package.json`, you will see the following environment variables, which are custom to this project and not defined by Next.js itself:\n\n* `NEXT_PUBLIC_NODE_ENV=development`: sets everything to be development by default.\n+\nIf this variable not given, `NODE_ENV` is used instead.\n+\nJust like `NODE_ENV`, this variable affects the following aspects of the application:\n+\n** if the Next.js server will run in development or production mode. From the Next.js CLI, this determination is done with `next dev` vs `next start`. But we use a custom server where both dev and prod are run from `./app`, and so we determine that from environment variables.\n** if the database will be SQLite (default development DB) or PostgreSQL (default production DB)\n** in browser effects, e.g. turns off Google Analytics\n+\nWe cannot use `NODE_ENV` here directly as we would like because and Next.js forces `process.env.NODE_ENV` to match the server's dev vs production mode. But we want a production mode server, and no Google analytics in this case.\n* `NODE_ENV_NEXT_SERVER_ONLY=production`: determines is the Next.js server will run in development or production mode.\n+\nThis variable only affects the Next.js server dev vs prod aspect of the application, and not any other aspects such as the database used and in browser effects such as having Google Analytics or not.\n+\nIf given, this variable overrides all others in making that determination, including `NEXT_PUBLIC_NODE_ENV`. If not given, `NODE_ENV` is used as usual.\n+\nIf this variable is not given, `NEXT_PUBLIC_NODE_ENV` is given instead.\n\n=== Local run as identical to deployment as possible\n\nHere we use PostgreSQL instead of SQLite with the prebuilt static frontend. Note that optimized frontend is also used on the SQLite setup described at \u003c\u003clocal-optimized-frontend\u003e\u003e).\n\nFor when you really need to debug some deployment stuff locally\n\nSetup:\n\n....\nsudo apt install postgresql\n\n# Become able to run psql command without sudo.\nsudo -u postgres createuser -s \"$(whoami)\"\ncreatedb \"$(whoami)\"\n\ncreatedb realworld_next\npsql -c \"CREATE ROLE realworld_next_user with login password 'a'\"\npsql -c 'GRANT ALL PRIVILEGES ON DATABASE realworld_next TO realworld_next_user'\necho \"SECRET=$(tr -dc A-Za-z0-9 \u003c/dev/urandom | head -c 256)\" \u003e\u003e .env\n....\n\nRun:\n\n....\nnpm run build-prod\nnpm run start-prod\n....\n\nthen visit the running website at: http://localhost:3000/\n\nTo \u003c\u003cgenerate-demo-data\u003e\u003e for this instance run:\n\n....\nnpm run seed-prod\n....\n\n=== Development run in PostgreSQL\n\nIf you want to debug a PostgreSQL specific issue interactively on the browser, you can run a development Next.js server on PostgreSQL.\n\nThis is similar to \u003c\u003clocal-run-as-identical-to-deployment-as-possible\u003e\u003e, but running the development server is more convenient for development as you won't have to `npmr run build-prod` on every frontend change.\n\nFirst setup the PostgreSQL database as in \u003c\u003clocal-run-as-identical-to-deployment-as-possible\u003e\u003e.\n\nThen start the server with:\n\n....\nnpm run dev-pg\n....\n\nTo run other database related commands on PostgreSQL you can export the `REALWORLD_PG=true` environment variable manually as in:\n\n....\nREALWORLD_PG=true ./bin/sync-db.js\nREALWORLD_PG=true ./bin/generate-demo-data.js\n....\n\nIf you need to inspect the database manually you can use:\n\n....\npsql realworld_next\n....\n\n== Heroku deployment\n\nThe setup is analogous to: https://github.com/Peter-Haro/node-express-sequelize-realworld-example-app#heroku-deployment but instead of `heroku git:remote -a cirosantilli-realworld-express` you should use:\n\n....\ngit remote add heroku-next https://github.com/Peter-Haro/node-express-sequelize-nextjs-realworld-example-app.git\n./heroku.sh addons:create --app cirosantilli-realworld-next heroku-postgresql:hobby-dev\n./heroku.sh config:set --app cirosantilli-realworld-next SECRET=\"$(tr -dc A-Za-z0-9 \u003c/dev/urandom | head -c 256)\"\n# Optional. If set, enables demo mode. We must use the NEXT_PUBLIC_* prefix for the variable name,\n# otherwise it is not visible in the page renders.\n./heroku.sh config:set --app cirosantilli-realworld-next NEXT_PUBLIC_DEMO=true\n....\n\nThis is done because this repository is normally developed as a branch of that one, which would lead to a conflicting name for the branch `heroku`.\n\nAdditionally, before deploying, you must also make sure that you can run the PostgreSQL tests by setting PG up as shown at: \u003c\u003clocal-run-as-identical-to-deployment-as-possible\u003e\u003e. This is because before deployment we always run the tests to ensure that nothing broke. And running the tests in PostgreSQL is particularly crucial, because since Sequelize is not stellar, sometimes things work in SQLite but fail in PostgreSQL.\n\nThen, to deploy the latest commit run:\n\n....\nnpm run deploy\n....\n\nThis also pushes the code to GitHub on success, and markes the deployed commit with the `deploy` branch on GitHub, to mark clearly the deployed commit clearly in case local development moves ahead of the deployed commit a bit.\n\nYou then have to add `--app cirosantilli-realworld-next` to any raw `heroku` commands to allow Heroku to differentiate between them, e.g.:\n\n....\n./heroku.sh run --app cirosantilli-realworld-next bash\n....\n\nfor which we have the helper:\n\n....\n./heroku.sh run bash\n....\n\ne.g. to delete, recreate and reseed the database:\n\n....\n./heroku.sh run bin/generate-demo-data.js --force-production\n....\n\nWe are not sure if Next.js ISR can be deployed reliably due to the ephemeral filesystem such as those in Heroku...: https://stackoverflow.com/questions/67684780/how-to-set-where-the-prerendered-html-of-new-pages-generated-at-runtime-is-store but it has worked so far.\n\n=== `devDependencies` vs `dependencies`\n\nNote that any dependencies required only for the build e.g. typescript are put under `devDependencies`.\n\nOur current \u003c\u003cheroku-deployment\u003e\u003e setup installs both `dependencies` and `devDependencies`, builds, and then removes `devDependencies` from the deployed code to make it smaller.\n\n=== Demo mode\n\nActivated with `NEXT_PUBLID_DEMO=true` or:\n\n....\nnpm run dev-demo\n....\n\nThis has the following effects:\n\n* block posts with tags given at `blacklistTags` of `config.js` The initial motivation for this was to block automated \"Cypress Automation\" spam that is likely setup by some bastard on all published implementations via the backend, example: https://archive.ph/wip/n4Jlx[], and might be taking up a good part of our Heroku dynos, to be confirmed.\n+\nWe've logged their IP as 31.183.168.37, let's see if it changes with time. That IP is from Poland, which is consistent with Google Analytics results, which are overwhelmingly from Poland, suggesting a bot within that country, which also does GET on the web UI.\n* whenever a new object is created, such as article, comment or user, if we already have 1000 objects of that type, delete the oldest object of that type, so as to keep the database size limited. TODO implement for Tags, Follows and Likes.\n* \"Source code for this website\" banner on top with link to this repository\n* clearer tags input message \"Press Enter, Tab or Comma to add a tag\"\n\n=== Heroku instance management\n\nGet a PostgreSQL shell:\n\n....\n./heroku.sh psql\n....\n\nor run a one-off Postgres query:\n\n....\n./heroku.sh psql -c 'SELECT * FROM \"User\"'\n....\n\nDELETE ALL DATA IN THE DATABASE and \u003c\u003cgenerate-demo-data\u003e\u003e inside Heroku:\n\n....\n./heroku.sh run bash\n....\n\nand then run in that shell:\n\n....\nbin/generate-demo-data.js --force-production\n....\n\nor you can do it in one go with:\n\n....\n./heroku.sh run bin/generate-demo-data.js --force-production\n....\n\nWe have to run `heroku run bash` instead of `heroku ps:exec` because the second command does not set `DATABASE_URL`:\n\n* https://stackoverflow.com/questions/62502951/heroku-env-variables-database-url-and-port-not-showing-in-dyno-heroku-psexec/68050303#68050303\n* https://stackoverflow.com/questions/48119289/how-to-get-environment-variables-in-live-heroku-dyno/64951959#64951959\n* https://www.reddit.com/r/rails/comments/ejljxj/how_to_seed_a_postgres_production_database_on/\n\nEdit a file in Heroku to debug that you are trying to run manually, e.g. by adding print commands, uses https://github.com/hakash/termit[] minimal https://en.wikipedia.org/wiki/GNU_nano[nano]-like text editor:\n\n....\n./heroku.sh ps:exec\ntermit app.js\n....\n\n== ISR vs SSR\n\nISR is an optimization that aims to:\n\n* reduce load times\n* reduce server load\n\nLike all optimizations, it makes things more complex, so you really have to benchmark things to see if you need them.\n\nAs mentioned at: \u003c\u003cssr-version\u003e\u003e, this is one of the main goals of this website.\n\nThe main complexity increase of ISR is that you have to worry about React `usEffect` chains of events after the initial page load, which can be very hard to debug.\n\nWith ISR, we want article contents and user pages to load instantly from a prerendered cache, as if the user were logged out.\n\nOnly after that will login-specific details be filled in by client JavaScript requests to the backend API, e.g. \"have I starred/favorited this article or not\".\n\nThis could lead to amazing article text loading performance, since this is the same for all users and can be efficiently cached.\n\nThe downside of that is that the user could see a two stage approach which is annoying, especially if there is no clear indication (first download, then wait, then if updates with personal details). This could be made even better by caching things client side, and `userSWR` which we already using likely makes that transparent, so there is hope. Even more amazing would be if it could cache across requests, e.g. from index page to an article! One day, one day, maybe with GraphQL.\n\nAnother big ISR limitation is that you can't force a one-off immediate page update after the user edits a post, a manual refresh is generally needed: https://github.com/vercel/next.js/discussions/25677[]. However, this is not noticeable in this website, because in order to show login-specific information, we are already re-fetching the data from the API after every single request, so after a moment it gets updated to the latest version.\n\nOur organizational principle is that all logged-in API data will be fetched from the toplevel element of each page. It will have the exact same form as the static rendering props, which come directly from the database during build rather than indirectly the API.\n\nThis data will correspond exactly to the static prerendered data, but with the user logged in. It will then simply replace the static rendered logged out version, and trigger a re-render.\n\nThis approach feels simple enough that it could even be automated in a framework manner. One day, one day.\n\nIt is true that the pass-down approach goes a bit against the philosophy of `useSWR`, but there isn't much we can do, e.g. `/` fetches all articles with `/api/articles`, and determines favorite states of multiple posts. Therefore, we if hardcoded `useSWR` for the article under `FavoriteArticleButton`, that would fetch the states from each article separately `/api/articles/[slug]`. We want that to happen on single article inspection however.\n\n=== SSR version\n\nWe are slowly building an SSR version of the website under the `/ssr` prefix. E.g. `/ssr` will be a SSR version of the ISR at `/`, `/ssr/login` of `/login`, and so on.\n\nThe most noticeable thing in SSR is if you open the DevTools that there are no `GET` requests to the `/api` after the page loads, except where we are forced to do them by the terrible design of Realworld not having separate URLs for pagination and some tabs.\n\nYou will also never see the loading spinner. The page will just load all at once in one go.\n\nThis will allows us to very directly compare if there are any noticeable user experience differences.\n\nTODO It would also be amazing to test server overload with this, but that is harder. One day.\n\n=== No flickering and automatic updates\n\nOur general ISR philosophy is: the only flickering or automatic page update allowed is from loading spinner to the final data.\n\nNew data can only ever happen if the user presses F5.\n\nWe do have one exception though: the front page, as it would be too confusing for users to not see their newly created post there. An update might happen on that page therefore.\n\nThis is the kind of thing that suggests that SSR is generally what you want for index/find pages.\n\n== What works with JavaScript turned off\n\nDue to ISR/SSR, \u003c\u003csingle-url-with-multiple-pages,all pages of the website that have distinct URLs\u003e\u003e, which includes e.g. articles and profiles but not \"Your Feed\" vs \"Global Feed, look exactly the same with and without JavaScript for a logged out user.\n\nFor the pages without distinct URLs, we don't know how to do this, the only way we can do it is by fetching the API with JavaScript.\n\nSSR would require `\u003ca href` elements to send custom headers, so that URLs won't be changed, which is impossible:\n\n* https://stackoverflow.com/questions/15835783/adding-http-request-header-to-a-a-href-link\n* https://stackoverflow.com/questions/374885/can-i-change-the-headers-of-the-http-request-sent-by-the-browser\n* https://softwareengineering.stackexchange.com/questions/250602/why-doesnt-the-html-dom-specification-allow-hyperlinks-to-set-an-accept-header\n\nSSG would, in addition to the previous, require specific Next.js support for the above.\n\nYou can turn JavaScript off easily on Chromium with this extension: https://github.com/maximelebreton/quick-javascript-switcher which adds the shortcut Alt + Shift + Q to toggle JavaScript.\n\n== TODO\n\n=== Single URL with multiple pages\n\nWe don't know how to have multiple pages under a single URL in Next.js nicely. This is needed for tab navigation e.g. under `/` \"Your Feed\" vs \"Global Feed\" vs tag search, and for pagination:\n\n* https://stackoverflow.com/questions/62628685/static-pagination-in-nextjs\n* https://stackoverflow.com/questions/65471275/material-ui-tabs-with-nextjs\n\nSuch \"multi page with a single URL\" website design makes it impossible to access such pages without JavaScript, which is one of the main points of Next.js for.\n\nOur implementation works around this by just fetching from the API and rendering, like a regular non-Next React app would, and this is the only way we know how to do it.\n\nWe do however render the default view of each page in a way that will work without JavaScript, e.g. the default page 0 of the global index. But then if you try and e.g. click the pagination buttons they won't do anything.\n\nGlobal discussion at: https://github.com/gothinkster/realworld/issues/691\n\n=== Personal user data in `/user/settings`\n\n`reck1ess` was using a mixture of SSR and client side redirects.\n\nIf you tried to access `/user/settings` directly e.g. by pasting it on the browser, it would redirect you to home even if you were logged in, and the server showed an error message:\n\n....\nError: No router instance found.\nYou should only use \"next/router\" inside the client side of your app.\n....\n\nWe patched to avoid that.\n\nHowever, we are still currently just using data from the `localStorage`. This is bad because if the user changes details on another device, the data will be stale.\n\nAlso this is a very specific case of personal user data, so it doesn't reflect the more general case of data that is not in `localStorage`.\n\nInstead, we should handle `/user/settings` from Next.js server side, notably check JWT token there and 401 if not logged in.\n\n=== TODO security\n\nUse a markdown sanitizer, the `marked` library `sanitize` option was deprecated.\n\n== Known divergences\n\nI aim to make this website look exactly like https://github.com/gothinkster/angular-realworld-example-app/tree/9e8c49514ee874e5e0bbfe53ffdba7d2fd0af36f pixel by pixel which we call \"our reference implementation, and have the exact same DOM tree as much as possible, although that is hard because Angular adds a gazillion of fake nodes to the DOM it seems.\n\nWe test this by running this front/backend, and then also running angular in another browser tab. We then switch between browser tabs quickly back and forth which allows us to see even very small divergences on the UI.\n\nSome known divergences:\n\n* reference shows \"Your Feed\" for logged out user, click leads to login. This just feels wrong, not done anywhere else.\n* https://github.com/gothinkster/angular-realworld-example-app/issues/202 \"No articles are here... yet\" clearly broken on Angular\n* `reck1ess` had implmented pagination really well with limits and previous/first/next/last, it would be a shame to revert that: https://github.com/gothinkster/realworld/issues/684\n\nError messages due to API failures are too inconsistent across implementations to determine what is the correct behaviour, e.g. if you patch:\n\n....\n--- a/api/articles.js\n+++ b/api/articles.js\n@@ -104,6 +104,7 @@ router.get('/', auth.optional, async function(req, res, next) {\n\n router.get('/feed', auth.required, async function(req, res, next) {\n   try {\n+    asdf\n     let limit = 20\n     let offset = 0\n     if (typeof req.query.limit !== 'undefined') {\n....\n\n* reference: shows \"Loading articles...\" forever, does not inform user about error\n* https://github.com/gothinkster/react-redux-realworld-example-app just throws an exception\n\n== Database migrations\n\nDatabase migrations are illustrated under link:[migrations].\n\nAny pending migrations are done automatically during deployment as part of `npm run build`, more precisely they are run from link:[bin/sync-db.js].\n\nWe also have a custom setup where, if the database is not initialized, we first:\n\n* just creates the database from the latest model descriptions\n* manually fill in the `SequelizeMeta` migration tracking table with all available migrations to tell sequelize that all migrations have been done up to this point\n\nThis is something that should be merged into sequelize itself, or at least asked on Stack Overflow, but lazy now.\n\n=== Debugging migrations\n\nIf a migration appears wrong, a good way to retry it after modifying the file under `migrations` is this oneliner:\n\n....\ngit add migrations \u0026\u0026 git commit -an \u0026\u0026 git checkout HEAD~ \u0026\u0026 bin/generate-demo-data.js \u0026\u0026 git checkout - \u0026\u0026 ./bin/sync-db.js\n....\n\n== Bugs\n\nhttps://github.com/reck1ess/next-realworld-example-app[] has several UI bugs/missing functionality, some notable ones:\n\n* https://github.com/reck1ess/next-realworld-example-app/issues/22 Your Feed not working. We fixed it at d98637bb10af2bb111f0f2a6ccc72c1de6c8f351.\n\nThe implementation of `reck1ess/next-realworld-example-app` felt a bit quirky in a few senses:\n\n* usage of `useSWR` even for data that can be already pre-rendered by Next.js such are articles. Presumably this is to give some kind of pool based realtime support? Or maybe it is just part of a workaround for the problem described at \u003c\u003csingle-url-with-multiple-pages\u003e\u003e. But that is not what other implementations do, and neither should we. We don't want data to update by surprise under a user's feet.\n* uses custom https://github.com/emotion-js/emotion[emotion-js] CSS in addition to the global http://demo.productionready.io/main.css[], which is also required since not everything was migrated to emotion.\n+\nWe later completely removed motion from this repository.\n+\nAnd also has a global `style.css`.\n+\nWhile this is good to illustrate that library, it also means that a lot of reimplementation is needed, and it is hard to be accurate at times.\n+\nAnd if it were to use emotion, it should be emotion only, without the global CSS. Instead, that repo uses both, sometimes specifying the same CSS multiple times in two ways.\n+\nIt is also very annoying that they used separated defined components rather than in-tree emotion CSS which can be done as:\n+\n....\n\u003cdiv css={css`\n  font-weight: 300;\n`}\u003e\n....\n+\nwhich leads to a much easier to read DOM tree, and less identifiers flying everywhere.\n+\nIt must be said that the port to emotion was made in a way that closely mimicks the original class/CSS structure. But still, it is just too much work, and mistakes popped up inevitably.\n\nThese are all points that we have or would like to address in this fork.\n\n== Alternatives\n\n* https://github.com/lifeiscontent/realworld[]: Rails backend is a downside, as it adds another language to the mix besides JavaScript. But it has graphql, which is really really awesome technology.\n\n== Keyboard shortcuts\n\nCtrl + Enter submits articles.\n\n== Debugging\n\n=== Step debugging\n\nFor the backend, add `debugger;` to the point of interest, and run as:\n\n....\nnpm run back-inspect\n....\n\nOn the debugger, do a `c` to continue so that the server will start running (impossible to skip automatically: https://stackoverflow.com/questions/16420374/how-to-disable-in-the-node-debugger-break-on-first-line[]), and then trigger your event of interest from the browser:\n\n....\nnpm run front\n....\n\n=== VERBOSE environment variable\n\nIf you run as:\n\n....\nVERBOSE=1 npm run dev\n....\n\nthis enables the following extra logs:\n\n* a log line for every request done\n\n=== Log database queries done\n\n....\nDEBUG='sequelize:sql:*' npm run start-prod\n....\n\n=== Generate demo data\n\nNote that this will first erase any data present in the database:\n\n....\n./bin/generate-demo-data.js\n....\n\nYou can then login with users such as:\n\n* `user0@mail.com`\n* `user1@mail.com`\n\nand password `asdf`.\n\nTest data size can be configured with CLI parameters, e.g.:\n\n....\n./bin/generate-demo-data.js --n-users 5 --n-articles-per-user 8 --n-follows-per-user 3\n....\n\nIf you just want to truncate the database with empty data use:\n\n....\n./bin/generate-demo-data.js --empty\n....\n\n=== Prevent the browser from opening automatically\n\nIn case you've broken things so bad that the very first GET blows up the website and further requests don't respond https://stackoverflow.com/questions/61927814/how-to-disable-open-browser-in-cra\n\n....\nBROWSER=none npm run dev\n....\n\nThis gives you time to setup e.g. Network recording in Chrome Developer Tools to be able to understand what is going on.\n\n=== Sequelize sometimes does not show the full stack trace\n\nThis is a big problem during development, not sure how to solve it: https://github.com/sequelize/sequelize/issues/8199#issuecomment-863943835\n\n== Linting\n\nThe following lint checks are run automatically as part of:\n\n....\nnpm run build-dev\n....\n\nfrom \u003c\u003clocal-optimized-frontend\u003e\u003e, but it can be good to isolate the command to speed up the development loop.\n\nRun typescript type checks:\n\n....\nnpm run tsc\n....\n\nRun eslint checks:\n\n....\nnpm run lint\n....\n\nThese lint checks include both:\n\n* https://github.com/prettier/prettier[prettier] checks, which do style checking. Since it is just style checks, any problems with those can be fixed automatically by prettier's auto-refactoring functionality with:\n+\n....\nnpm run format\n....\n* more functional checks, including important checks such as those provided by eslint-config-react-hooks  as opposed to more functional thing \n\nRationale for some rules we've disabled:\n\n* `@next/next/no-img-element`: Next insist that you whitelist servers, which is only possible if we implement profile picture upload: https://github.com/Peter-Haro/node-express-sequelize-nextjs-realworld-example-app/issues/16 We will actually do this at some point.\n* `import/no-anonymous-default-export`: what's the point??? It just duplicates module names as pointed in comments. https://stackoverflow.com/questions/64729264/how-can-i-get-rid-of-the-warning-import-no-anonymous-default-export-in-react\n\n== Testing\n\nWhen running:\n\n....\nNODE_ENV=test npm run dev\n....\n\nthe server runs on a temporary in-memory database when using the default SQLite database.\n\nIt has no effect on \u003c\u003cdevelopment-run-in-postgresql,PostreSQL\u003e\u003e, as we don't know of any reasonable alternatives unfortunately. We could grant a create database privilege to our PostgreSQL test user... but Sequelize does not seem to support database creation there: https://stackoverflow.com/questions/31294562/sequelize-create-database/32212001[].\n\nOne implication of this is that it is not currently possible to run \u003c\u003ctest-js\u003e\u003e in parallel mode for PostgreSQL.\n\n=== test.js\n\nOur tests are all located inside link:test.js[].\n\nThey can be run with:\n\n....\nnpm test\n....\n\nRun just a single test:\n\n....\nnpm test -- -g 'substring of test title'\n....\n\nShow all queries done in the tests:\n\n....\nDEBUG='sequelize:sql:*' npm run test\n....\n\nTo run those tests on PostgreSQL intead, first setup as in \u003c\u003clocal-run-as-identical-to-deployment-as-possible\u003e\u003e, and then:\n\n....\nnpm run test-pg\n....\n\nNote that this will erase all data present in the database used. In order to point to a custom database use:\n\n....\nDATABASE_URL_TEST=postgres://realworld_next_user:a@localhost:5432/realworld_next_test npm run test-pg\n....\n\nWe don't use `DATABASE_URL` when running tests as a safegard to reduce the likelyhood of accidentaly nuking the production database.\n\nThe tests include two broad classes of tests:\n\n* API tests: launch the server on a random port, and run API commands, thus testing the entire backend. These are similar to the \u003c\u003crealworld-api-tests\u003e\u003e, but don't require postman JSON insanity, and start and close a clean server for every single test\n* smaller unit tests that only call certain functions directly\n* TODO: frontend tests: https://github.com/cirosantilli/node-express-sequelize-nextjs-realworld-example-app/issues/11\n\n==== Next.js tests\n\nBy default \u003c\u003ctest-js\u003e\u003e does not run any tests on Next.js, only on the API routes, because Next.js would make tests too slow:\n\n* Next.js startup is slow\n* we must run in production mode because development mode is too lenient, e.g. it does not raise 500 errors. Therefore we have to build before every run.\n\nTo also run tests that hit Next.js run:\n\n....\nnpm run test-next\n....\n\nor for Postgres:\n\n....\nnpm run test-pg-next\n....\n\n=== Realworld API tests\n\nThese tests are part of https://github.com/gothinkster/realworld which we track here as a submodule.\n\nTest test method uses Postman, but we feel that it is not a very good way to do the testing, as it uses JSON formats everywhere with embedded JavaScript, presumably to be edited in some dedicated editor like Jupyter does. It would be much better to just have a pure JavaScript setup instead.\n\nThey test the JSON REST API without the frontend.\n\nFirst start the backend server in a terminal:\n\n....\nnpm run back-test\n....\n\n`npm run back-test` will make our server use a clean one-off in-memory database instead of using the default in-disk development `./db.sqlite3` as done for `npm run back`.\n\nThen on another terminal:\n\n....\nnpm run test-api\n....\n\nRun a single test called `Register` instead:\n\n....\nnpm run test-api -- --folder Register\n....\n\nTODO: many tests depend on previous steps, notably register. But we weren't able to make it run just given specific tests e.g. with:\n\n....\nnpmr test-api -- --folder 'Register' --folder 'Login and Remember Token' --folder 'Create Article'\n....\n\nonly the last `--folder` is used. Some threads say that multiple ones can be used in newer Newman, but even after updating it to latest v5 we couldn't get it to work: \n\n* https://stackoverflow.com/questions/60057009/how-to-run-single-request-from-the-collection-in-newman\n* https://stackoverflow.com/questions/52519415/how-to-read-two-folder-with-newman\n\n== Authentication\n\nAs specified by Realworld, we use JWT authentication.\n\nThis can happen in two ways:\n\n* `Authentication` header (\"standard JWT\"): sent to the API routes. Immune to XSS. Stored in https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage[window.localStorage]. Requires JavaScript.\n* a cookie that contains a copy of the JWT, used only on safe HTTP methods, notably GET.\n+\nThe goal of the cookie auth is allow true SSR, and reduce complexity (`useEffect` debugging hell). This way, on every page load Next.js immediately sees that the user is logged in, and `getServerSideProps` is able to return the appropriate page rendered for that specific user by reading the cookie in the request headers. Note that is not possible in ISR.\n\nBecause cookies are used exclusively for safe methods, we don't need to worry about implementing the https://security.stackexchange.com/questions/8264/why-is-the-same-origin-policy-so-important/72569#72569[synchronizer token pattern].\n\nCurrently the login page requires JavaScript, so you can only login with JavaScript. But at some point we could enable a non-JavaScript method for that login, which would allow users to view logged-in-only pages too without JavaScript. They just won't be able to use any non-safe methods. But meh, non-JavaScript is for bots.\n\n== Benchmarks\n\nMethodology:\n\n* time after click event https://stackoverflow.com/questions/67750849/how-to-filter-by-event-type-in-chrome-devtools-profile-tab-e-g-to-see-mouse-cli/67750850#67750850 up until new page renders, not considering any images on the new page, just text\n* caches warmed by clicking all pages involved just before the experiment\n* hardware: Lenovo ThinkPad P51\n* browser: Chromium 91\n\n== Anti-spam\n\nThat website has no signup verification mechanism, users can just spam it at will via API.\n\nHowever, until someone decides to spam nonstop 24/7 to the point of actually preventing other users from viewing their own posts, it doesn't matter that much. Remember that in \u003c\u003cdemo-mode\u003e\u003e we limit the ammount of articles and comments, so unless we implement further restrictions, a spammer could easily replace all data with their own.\n\nSome things we could do include:\n\n* log IPs. Started doing that at https://github.com/cirosantilli/node-express-sequelize-nextjs-realworld-example-app/commit/f2ee0bea8c081fbd6bb42052a15ed55f3909ab3f on account creation. The only way to check IPs currently is through direct database access on:\n+\n....\n./heroku.sh psql\n....\n+\ne.g.:\n+\n....\n./heroku.sh psql -c 'SELECT username, email, ip, \"createdAt\" FROM \"User\" WHERE ip IS NOT NULL ORDER BY ip ASC, \"createdAt\" ASC'\n....\n+\nor maybe to find potential bots by IPs with most accounts:\n+\n....\n./heroku.sh psql -c 'SELECT ip, COUNT(ip) FROM \"User\" WHERE ip IS NOT NULL GROUP BY ip ORDER BY COUNT(ip) DESC, ip ASC'\n....\n+\nOf course, IP checks can be overcome with Tor, and blocking IPs is really messy because you can take down entire institutions.\n* captcha for signup. Captcha for post creation would be too annoying. This would immediatly block any bots, but not manual spammers.\n+\nhcapcha looks decent: https://docs.hcaptcha.com/[]. We have to make a request from our server to theirs to verify user login.\n* limit number of articles and comments per user. So spammers would need to create new accounts, and therefore redo captchas. 25 posts per account feels like enough.\n\nSome spam events:\n\n* 2021-12-22 https://archive.ph/EVcUw spammed a bunch of pro Chinese government messages, see also: https://github.com/cirosantilli/china-dictatorship and https://cirosantilli.com/china-dictatorship/backlinks\n+\nDid a bit of updating with `./heroku.sh psql`:\n+\n....\n./heroku.sh psql -c \"UPDATE \\\"Tag\\\" SET name = REPLACE(name, '', '六四事件法轮功新疆再教育營')\"\n....\n\n== Optimization\n\nImplementing something something without any efficiency considerations is one thing.\n\nImplementing it with efficiency is another.\n\nWe tried a bit to achieve the following, TODOing where we know we failed:\n\n* minimize API calls to the minimum. It can be easy to make multiple unecessary API calls with React if don't have a clue what you are doing, especially while waiting to decide if we are logged in or not, which must be done from `useEffect`: https://stackoverflow.com/questions/54819721/next-js-access-localstorage-before-rendering-page/68136224#68136224[]. We often have to differentiate between: \"we are logged off\" and \"we don't know our logged in status yet\". \n* minimize database calls, notably use single JOINs wherever possible, especially on the index page where lots of articles are brought in. This is hard in part due to the inflexibility of sequelize, some notes at: https://github.com/cirosantilli/node-express-sequelize-nextjs-realworld-example-app/issues/5\n*\n\n== Directory structure\n\n* \u003c\u003cdatabase-migrations\u003e\u003e related:\n** link:[bin/sync-db.js]\n** link:[migrations]\n* \u003c\u003ctesting\u003e\u003e related:\n** link:test.js[]: \u003c\u003ctest-js\u003e\u003e\n* link:front/[]: files that are safe to import from the frontend, all requires from inside `front/` should also be inside `front/` see: https://stackoverflow.com/questions/64926174/module-not-found-cant-resolve-fs-in-next-js-application/70363153#70363153 As mentioned there, everything that is usable in the frontend is also usable in the backend since we are an SSR setup. But not the other way around. `pages/` can contain a mixture of frontend and backend in each file however when HoCs are not being used and things don't blow up.\n** link:back/[]: this directory contains the backend corresponding to pages in link:front[], e.g. link:back/ArticlePage[] contains the `getStaticPaths` and `getStaticProps` that correspond to link:front/ArticlePage[]. Other backend-only files are in general placed anywhere outside of the link:front/[] directory.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpeter-haro%2Fnode-express-sequelize-nextjs-realworld-example-app","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpeter-haro%2Fnode-express-sequelize-nextjs-realworld-example-app","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpeter-haro%2Fnode-express-sequelize-nextjs-realworld-example-app/lists"}