{"id":16222590,"url":"https://github.com/moishinetzer/little-emperors","last_synced_at":"2025-04-08T01:40:24.884Z","repository":{"id":216153454,"uuid":"740105113","full_name":"moishinetzer/little-emperors","owner":"moishinetzer","description":null,"archived":false,"fork":false,"pushed_at":"2024-01-08T17:09:03.000Z","size":6696,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-04T20:43:13.762Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://little-emperors.fly.dev","language":"TypeScript","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/moishinetzer.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":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-01-07T14:44:13.000Z","updated_at":"2024-01-08T17:09:42.000Z","dependencies_parsed_at":null,"dependency_job_id":"4d2e18f2-2894-4104-b82f-bb3bd8e6cc32","html_url":"https://github.com/moishinetzer/little-emperors","commit_stats":null,"previous_names":["moishinetzer/little-emperors"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moishinetzer%2Flittle-emperors","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moishinetzer%2Flittle-emperors/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moishinetzer%2Flittle-emperors/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moishinetzer%2Flittle-emperors/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/moishinetzer","download_url":"https://codeload.github.com/moishinetzer/little-emperors/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247761051,"owners_count":20991533,"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":[],"created_at":"2024-10-10T12:14:37.000Z","updated_at":"2025-04-08T01:40:24.854Z","avatar_url":"https://github.com/moishinetzer.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Moishi Netzer - Hotel List Web App\n\n\u003c!-- Flex: --\u003e\n\u003cp float=\"left\" style=\"display: flex; flex-direction: row; justify-content: center; align-items: center;\"\u003e\n  \u003cimg src=\"public/Screenshot-hotels.png\" width=\"40%\" /\u003e\n  \u003cimg src=\"public/Screenshot-single-hotel.png\" width=\"40%\" /\u003e\n\u003c/p\u003e\n\n## Installation\n\nFirst, install dependencies:\n\n```sh\nnpm install\n# I love bun in which case try:\nbun install\n```\n\nGenerate the Prisma client for DB access and type-safety:\n\n```sh\nnpx prisma migrate dev\n# bun:\nbunx prisma migrate dev\n```\n\nThen, run the project:\n\n```sh\nnpm run dev\n# Again, I implore you to try bun:\nbun run dev\n```\n\nThat's it!\n\n## Overview\n\nThe solution I have opted for is a full-stack application using the following technologies:\n\n- [Remix](https://remix.run/docs) as the main full-stack driving framework for React\n  - Remix acts as both the backend and the frontend\n  - Running off of a [Vite](https://vitejs.dev/) server\n- [TailwindCSS](https://tailwindcss.com/) for styling\n- [Prisma](https://www.prisma.io/) as an ORM for both generating the DB client and accessing the DB in a type-safe fashion\n- [SQLite](https://www.sqlite.org/index.html) is the database being used for this application for reasons listed below\n- [Prettier](https://prettier.io/docs) for formatting\n- [ESLint](https://eslint.org/) for linting with sensible defaults provided\n\n## Backend\n\nThe backend is provided by Remix. Whilst this may seem counter-intuitive it allows strong typing between the server and client, and it balances the problem of colocating server-side with client-side but it also provides great paradigms for providing the data.\n\n\u003e Quick Intro\n\nIn Remix, you use loaders to define endpoints so for the following:\n\n```tsx\n// \u003e mysite.com/hotels\nexport function loader() {\n  return json({\n    hotels: await getHotels(),\n  });\n}\n```\n\nThis prompts Remix to create an endpoint at the `/hotels` route. exporting a `loader` creates a `GET` handler, and exporting an `action` creates a `POST` handler.\n\nRemix uses this `loader` to fetch the data BEFORE the page loads, essentially blocking page load until the data is available for the client to use. This means no need to handle a pending state whatsoever. When you load the page it is fully hydrated with your data.\n\nUsing the data looks like this:\n\n```tsx\nexport function loader() {\n  // ...\n}\n\nexport default function HotelsRoute() {\n  // `hotels` is now fully typed and always available\n  // on the page load to be used across the component\n  const { hotels } = useLoaderData\u003ctypeof loader\u003e();\n}\n```\n\nSometimes however you want the page to load first and stream the result in later, this is where Remix `defer` comes in. It's an implementation of React's `Suspense` component.\n\nI have implemented an alternative index route which implements this behaviour in [`/app/routes/deferred.tsx`](app/routes/deferred.tsx)\n\nThe beauty of using Remix is that your routes provide the HTML but can also deliver the raw JSON to be reused in your native apps and can be a backend for any frontend you wish to hook up!\n\nFor example, whilst running this app, try a GET request with the following:\n\n```sh\n# Don't worry too much about the \"?index\u0026_data\" part - that's a remix implementation that's being improved soon\n# Get all hotels\ncurl \"localhost:5173/?index\u0026_data\"\n\n# Get a specific hotel id=3\ncurl \"localhost:5173/3?_data\"\n```\n\nNotice how all the hotels are being sent as a JSON response! This is the beauty of remix. Even using this to proxy to another API, e.g. Laravel 😉 allows you to use this as your central API hander if you want a single source of truth for all of your frontends, React, React Native - anything really.\n\n## Frontend\n\nAs for the frontend you will notice there is no `fetch` anywhere because Remix handles all of that logic for us. (Mind you, they don't stub `fetch` either, which is a common NextJS footgun)\n\nNotice how few `useState` and `useEffect` calls there are throughout the whole application. It is beautiful in its simplicity, UX and legibility of code.\n\n## Database\n\nJust a quick note about the DB and the decisions I made for this project.\n\nI decided to go with SQLite, because not only is it lightweight and simple which is perfect for this task, but because I have it saved to a file, there is no installation step required for you to run this project locally such as hooking up a database. Prisma hooks up all the calls from the locally saved DB which I have pushed to GitLab, (Of course I would deploy a DB in a production setting and not track it in version control)\n\nYou will notice at the bottom of my `package.json` that I have the following:\n\n```json\n{\n  // ...\n  \"prisma\": {\n    \"seed\": \"npx tsx scripts/import-data.ts\"\n  }\n}\n```\n\nWhen Prisma generates the initial data it seeds the database. The beauty of running a ts script is that the file can be run manually and after you look at it you will see it is implemented with flexibility in mind so that seeding new/more data is easily possible when needed.\n\nFor the future of course it would be easier to implement full CRUD into this app, however, I believe this was out of the scope of the requested task.\n\n## Fin\n\nThank you very much for reviewing this, and I hope you enjoy it as much as I enjoyed creating it!\n\nBest regards,\nMoishi.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoishinetzer%2Flittle-emperors","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmoishinetzer%2Flittle-emperors","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoishinetzer%2Flittle-emperors/lists"}