{"id":15192518,"url":"https://github.com/petergarnaes/madklub-website","last_synced_at":"2026-02-21T20:04:18.273Z","repository":{"id":78474387,"uuid":"103397360","full_name":"petergarnaes/Madklub-website","owner":"petergarnaes","description":"Website for planning and coordianting comunal dinners. Written in Node.js and React. Implements a GraphQl API. Built with Webpack","archived":false,"fork":false,"pushed_at":"2017-11-14T13:05:36.000Z","size":417,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-01-11T14:31:29.219Z","etag":null,"topics":["code-splitting","graphql-api","graphql-query","react","react-router","redux","sequelize","server-side-rendering","webpack2"],"latest_commit_sha":null,"homepage":"https://rocky-gorge-46272.herokuapp.com/","language":"JavaScript","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/petergarnaes.png","metadata":{"files":{"readme":"README.md","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":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-09-13T12:32:55.000Z","updated_at":"2017-09-18T07:43:18.000Z","dependencies_parsed_at":"2023-03-13T20:13:28.946Z","dependency_job_id":null,"html_url":"https://github.com/petergarnaes/Madklub-website","commit_stats":{"total_commits":167,"total_committers":1,"mean_commits":167.0,"dds":0.0,"last_synced_commit":"123e4c9bd26891aad9c74ae94ba1339060169b57"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/petergarnaes%2FMadklub-website","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/petergarnaes%2FMadklub-website/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/petergarnaes%2FMadklub-website/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/petergarnaes%2FMadklub-website/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/petergarnaes","download_url":"https://codeload.github.com/petergarnaes/Madklub-website/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241255956,"owners_count":19934960,"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","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":["code-splitting","graphql-api","graphql-query","react","react-router","redux","sequelize","server-side-rendering","webpack2"],"created_at":"2024-09-27T21:41:23.071Z","updated_at":"2025-10-23T20:52:19.672Z","avatar_url":"https://github.com/petergarnaes.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Madklub Web App\n\n## Summary\n\nWebsite for arranging comunal dinners with the people you share a kitchen \nwith in a kollegium. Users can sign up to cook meals, and participants can \nbring guests and cancel. Cooks can modify meal description, and complete \nshopping, which disables further changes to participation.\n\nThe webiste offers a custom frontpage, depending on if the user is a participant\nor cook, where you can perform the most common actions relataed to dinners.\n\nThere is also a calendar view where dinners can be browsed in a calendar and \ndinners are color coded to quickly see the status.\n\n### Utilized tech\n\n* Webpack - all-in-one tool for bundling, transpiling using babel and hot \nreloading code\n* Babel - Transpiles React classes in es6 style and es6 features in general \nto es5 (which can run in browsers)\n* Node.js - Server running javascript, good for light weight thread work (ie. \nnormal website work) and ideal for Universal Apps with server side rendering \nto deliver an optimal experience. Very mature environment with many tools for \neverything from internationalization, authorization to API.\n* Express - Makes Node.js way more practical, by using middleware. Makes stuff \nlike authorization and routing outside the App a breeze.\n* React - UI component library, and thanks to babel we can write them with \n`.jsx` decorators, making it readable and more declarative.\n* React Router - Easy in-app routing with built in history handling. Very\ndeclarative, builds upon the strengths of react.\n* Redux - Flux implementation, making the app even more modular and functional\n* Bootstrap - Predefined CSS and components with great options for customizing\nget a beautiful website. Antd (ant design) is an alternative, seems very \nsimilar, only a few components differ.\n* GraphQL - An API supporting queries, so changes in frontend data dependencies\ndoes not require changes in backend API. Also ensures we never overfetch, or \nmake multiple API calls for one page.\n* Apollo - Easy way to declare data dependencies for individual react \ncomponents, and constructs the GraphQL query for you when loading the \ncomponents, as well as call the GraphQL API to provide the data. Also plays \nnice with Redux.\n* JWT - Smart way to deal with tokens and login sessions for the app.\n* Passport - Authorization middleware, with possible Facebook integration!\n* bcrypt or scrypt - Encryption and salting tool for storing passwords.\n* sequelize - Construct queries in javascript, use promise chaining to easily\nconstruct transactions depending on multiple dependent mutations.\n* graphql-sequelize - Can with a given sequelize DB model help you construct\nyour graphql schema and resolve graphql queries into sql queries.\n\n### Things to consider\n\n* All of app stylesheet is statically included in header, so the site does not \nflash unstyled. Ideally we have [critical rendering path](https://developers.google.com/web/fundamentals/performance/critical-rendering-path/)\nwhich is possible with either [container stuff](https://github.com/kriasoft/isomorphic-style-loader)\nor a more self implemented way doing stuff with css modules and load them like \nin react-redux-universal-hot-example. This should be an experimental \nafterthought, as static will be fine.\nWe need to define a [custom stylesheet](http://getbootstrap.com/customize/) \nthat does not include style for components we do not use. For production we \nshould also minify the css which `css-loader` is capable of.\n* Use *above the fold rendering*, to defer some rendering to client side.\nCould be useful for all the less important stuff on a page, or just \nexpensive stuff that would make the page hang, for example a complicated \ngraph or something. In that case a loading icon is better. Ideally we \ncould package it so Server Side checks for *above the fold*, while client\ndoes not waste time checking. Check if async server side has exactly \nthis effect, I think it might have. If it has, we should expand the \nfunctionality so that we can prefetch in html header regardless, for \nslightly faster client side render when we defer.\n* [electrode-react-ssr-caching](https://github.com/electrode-io/electrode-react-ssr-caching).\nIs a way of cahing and templating *simple* react components, so the \njavascript does not need to run every time we render the component. A \ngreat example would be our navigation bar, which is static except for \nthe username. This library supports templates with simple prop \nreplacements, so the navigation bar could be rendered a lot faster.\n* Pre-cache remaining routes using service workers, see `sw-precache`\n* Maybe use [node-ensure](https://github.com/bauerca/node-ensure) for \nsynchronous SSR of components instead of our complicated setup?\n* Consider HTTP/2 server push, so assets can be sent along with request\nfor page, for better load speeds.\n* [HTTP/2](https://webapplog.com/http2-node/) + \npreload should be implemented, try and see how fast and how little \nwaterfall we have with HTTP/2. We could also use the preload system to\nfigure out what to push. Use `spdy` on `npm` to do HTTP/2.\n* HTTP/2 does not seem worth it, slightly faster but not fully supported.\n\n# Notes about the project\n\n## Setup Test Environment\n\n### Requirements\nNode 6.0 required, as well as a sqlite driver for test database.\n\n### Starting the test server\nTo get going with the development server, first install dependencies with \n`npm install`. Then simply run `npm run-script start-dev-server`. This \nwill start the development server on port 3000. This setup includes hot \nreloading of react components, so styling and development should be very \nfast.\n\n### Test data\nTo get some test data into the system, when the sequelize db is syncing change\n`force` to `true` and uncomment `testDb(db)` first time you start the server.\nThen you can change it back, as it will be there forever.\n\nIf you don't change it back, it will overwrite it on every restart, which makes\ntesting mutations hard.\n\nAnother way is to build for production, by running `npm run-script build-server`\nand then `node dist/test_db.js`.\n\n### Testing environment\nChanges to the server and anything in the `server` folder will trigger a \nre-bundling of the server. This is slow compared to hot-reloading, but still\ngreat for prototyping the server.\n\nNotice that changes to `app` folder triggers rebundling of client side bundling.\nThis is not the same as server side, so when refreshing you will not get a page\nyou just made, but when bundle arrives you will. The console will probably also\nwarn you your server side rendering is misbehaving, but don't worry! Just\nre-bundle the server.\n\n### Differences from production\n\nIn order for hot module reloading to work, all of the app has to be loaded\nsynchronously. This means we do the same sync/async swap as we do to make the\nserver render async components. No code splitting at all.\n\nAs for css, we use the css loader which loads css through javascript. This\nmakes sure css is loaded on HMR. In development it is not bundled and sent\nseparately, as we do in production.\n\n## Project structure\n\nMostly clear... This is so far:\n\n* `server`: Contains all server specific code. Contains GraphQL specific code,\npassport schemes and database configs. To level shema is in `schema.js`\n* `server/api`: Contains GraphQL specific code, as well as database \nconfiguration.\n* `server/api/db`: Database configuration, uses `sequelize` to set up and call \nan SQL database. `index.js` exports the sequelize shema of the entire DB, to\neasily construct the GraphQL shema and resolve with `graphql-sequelize`.\n* `server/api/types`: Contains our defined types, which is the meat of our \nschema.\n* `server/api/queries`: Top level types in our schema, to simplify `schema.js`\ngreatly. Also contains queries that are not representations of the database, \nlike current user session info, potentially user settings or server info.\n* `server/api/mutations`: Contains all mutations possible in our schema.\n* `client`: Contains client specific code. As of now it simply contains the \nbrowser specific setup for rendering the app. Browser specific is setting up \nhistory with the router, getting initial store state from HTML header sent from\nserver into Redux, setting up Apollo and such.\n* `app`: Folder containing the app, ie. the shared code between client and\nserver.\n* `app/components`: Contains a folder for each component in the app. As of now \neach folder will mostly just contain the `index.js` file, as it can all be\ncontained within the app. Here everything from Redux/Apollo containers to \n`mapDispatch` happens, as well as UI.\n* `app/actions`: Files with methods for producing actions of different kinds.\n* `app/reducers`: Reducers that can with any given action and state produce\na new state. New state should be copy, ie. reducer is pure. They are all\nexported in `app/reducers/index.js`, and combined on both server and client to\nform the entire state.\n* `app/public`: Public assets like images, .svg's etc. Copied when building \napp.\n* `app/themes`: Holds the styling (css) of the app.\n* `app/async`: Holds utilities for asynchronous loading. Does not need to be\nchanged unless behavior of asynchronous components change.\n\n## Building App\n\nWhen running `npm run build` as of now, the app will be built in the\n`dist/public` folder. The idea is that the files there is the complete client\nside app. For production, names include chunk hash, with means they change and\ncan be cache busted on updates.\n\nServer is also transpiled and bundled with webpack. For production build, \nuglifying and no hot reload can be used exactly like with client, but with \n`target: node` of course, as we see in `webpack.config.server.dev.js`. The\nserver is put in `dist/backend.js`.\n\nTo put into production run these commands:\n* `npm run-script build`: Will build all client side assets, ie. vendor code,\nasync chunk, css etc.\n* `npm run-script build-server`: Will build server\n* `node dist/backend.js`: Will start production server\n\n## Security\n\nSecurity is a key concern, and there are many pitfalls. This app goes for a\nstateless authentication, as this is the simple and most flexible model for \nthe user.\n\n* HTTPS - A users interaction with the web page should be encrypted. Use lets \nencrypt (or something?) for free HTTPS? This also ensures no Man in the Middle \nattacks that can sniff up JWT token.\n* passport - Allows using various strategies to verify users credentials.\nThis way a company with more means and experience in protecting users\ninformation can be used.\n* bcrypt - Strong password encryption with salt for local password storage.\n* JWT - Authentication token tool, for safely encrypting a json object and \nusing it as the authentication token. When this token is sent to the server \nwe can decrypt it and retreive user ID, so successful decryption is equivalent \nto authentication of the user with the decrypted user ID. Timestamp and \nexpiration is handled by library.\nWe only transfer this token over https ie. `secure: true`, and it is `httpOnly`\nbecause we might as well.\n* Dealing with CSRF - We use the Double Submit Cookie pattern, which together \nwith same-origin policy (default) ensures that it requires JavaScript to make \na valid request, which by same-origin is only possible in the page we served, \nand not some malicious third-party site, or any sort of link the user is \ntricked into clicking.\n* Dealing with XSS - All string inputs must be sanitized! Even though session \nJWT token is `httpOnly` XSS could still use logged in session, and traverse \nany CSRF method. While they can not get the JWT token, they can still send \nrequest from the browser as the logged in user. Also, we JSON.stringify \nthe state into a `\u003cscript\u003e` tag, so unsanitized input can potentially \nescape script and start new script tag running anything.\n\nMore notes on public API in Random Notes.\n\nAll mutations should be verified if the user is allowed to do the mutation in \nquestion. Does the user have the right permissions? Is time restrictions and \ndeadlines held? All mutations with several steps should be transactions. \nWrites should be infrequent, so transactions will do.\n\nALL INPUT MUST BE SANITIZED! Seems like it is default, so no html \ncharacters, I could not inject through meal;\n\n## Structure and architecture\n\nThis is an isomorphic app, with _all_ styling pre-bundled, and delivered on any\npage load. The philosophy is that we stick mainly to bootstrap, with only a few\nextensions which does not increase bundle size that much.\n\nBootstrap is fairly well optimized for the modern browser, so pulling the whole\nthing is not to bad (ca. 118 kb). This also seem to be the standard, as there\nare not a lot of work done on isomorphic style loading (only one repo as far as\nI can see). Can be compressed with gzip to something like 20 kb.\n\n### Async components\n\nTo keep the bundle size of the app to a minimum, we use code splitting to make\nall non-immediate components split from the `main` bundle. These can then be\ndownloaded on demand. It all hinges on the `import()` statement.\n\nAt compile time, webpack takes any `import()` statement, dereference its path\nand bundles that code __with__ dependencies into a separate bundle. When that\nsame `import()` statement is reached at run-time, it returns a promise that\nreturns the module we split of.\n\nA problem arises though... These components should _only_ be asynchronous on\nthe client. To solve this problem, we do make a module that uses `require`\ninstead of `import()` because it is synchronous, and with\n`NaromalModuleReplacementPlugin` make the server use the synchronous module\nwhen bundled up.\n\nThis however creates yet _another_ problem. Even though the client gets the\npage server-side rendered, when running the bundle code, it thinks it has to\nload the component asynchronously. Because asynchronous components don't show\nwhile loading, this happens:\n\n* Client gets server-side rendered page in all its glory!\n* Bundle starts loading the required component with all the code necessary to\nrun it, but while it waits, shows nothing, ie. __removes__ the glorious\nsever-side rendered content.\n* Component is asynchronously loaded, mounts the component and everything is\ngood again.\n\nSo as we can see, our efforts are ruined. We want the server to tell the client\nwhich async components are already rendered, and make the client wait for those\ncomponents to load before rendering the app.\n\nThis is communicated through Redux. When server renders an async component, it\nfires a `register` event, which makes the store remember the key for the\ncomponent it rendered. Before the client renders, it waits for all the async\ncomponents indexed by the keys the server sends. These components are stored in\na map, again indexed by their keys. This is so when the async route is rendered\nand it discovers that the server has already rendered it, it uses the component\nfrom the \"loaded components\" map instead of loading it itself through\n`import()`.\n\nThis works, and has some added benefits!\n\n* Hot Module Reloading can still be achieved if the client synchronously loads\ncomponents instead of asynchronously. This is a simple matter of bundling the\nclient with the same `NaromalModuleReplacementPlugin` trick as with the server.\n* We can prevent waterfall downloading, by taking the registered routes after\nthe app rendered on the server side, and use those keys to add `\u003cpreload /\u003e`\nstatements to the header on the server side.\n\nThe downside is that it introduces overhead, and is not very declarative.\nTwo files must be created for each route we want to load asynchronously, and\nthe key must be correct in a few places for it to work. The overhead is not\nto bad, as no tree walking occurs (which other solutions implement). The gains\nwe achieve also greatly outweigh the overhead. With small app components, the\ncode for any page of the app will download very fast, and even faster with the\n`\u003cpreload /\u003e` trick. These components are also cacheable, so revisits are fast.\nThe overhead in constructing the `\u003cpreload /\u003e` serverside is small.\n\n## Random Notes\n\n### Requirements\n\nTarget browser must be newer browser that supports HTML5, because of its\nbrowser history API. It is possible to instruct our router to do full refresh\non every navigation, which we would do for non-HTML5. This can get complicated\nfast though, so for now just don't support.\n\n# Public API security\n\nIf we wanted our GraphQL API to be public, we could give out an API key, which \nis just another JWT token but with a different secret/header. Our API should \nthen be able to verifiy either cookie+csrf or HTTP header with \n`Authorization: Bearer jwt-api-key` which is the standard OAuth pattern. A \nseparate login page might be in order here.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpetergarnaes%2Fmadklub-website","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpetergarnaes%2Fmadklub-website","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpetergarnaes%2Fmadklub-website/lists"}