{"id":24132846,"url":"https://github.com/formcms/food-truck","last_synced_at":"2025-09-19T02:31:55.973Z","repository":{"id":240303390,"uuid":"802181290","full_name":"formcms/food-truck","owner":"formcms","description":null,"archived":false,"fork":false,"pushed_at":"2024-05-18T13:41:27.000Z","size":2177,"stargazers_count":9,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-10T20:25:18.933Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","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/formcms.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-05-17T17:17:54.000Z","updated_at":"2025-01-07T17:58:21.000Z","dependencies_parsed_at":"2024-08-19T03:55:51.568Z","dependency_job_id":null,"html_url":"https://github.com/formcms/food-truck","commit_stats":null,"previous_names":["jaikechen/food-truck","fluent-cms/food-truck","formcms/food-truck"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/formcms%2Ffood-truck","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/formcms%2Ffood-truck/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/formcms%2Ffood-truck/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/formcms%2Ffood-truck/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/formcms","download_url":"https://codeload.github.com/formcms/food-truck/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":233546489,"owners_count":18692223,"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":"2025-01-11T22:38:39.897Z","updated_at":"2025-09-19T02:31:50.410Z","avatar_url":"https://github.com/formcms.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"# Food trucks \nThis is a demo project showcase usage of Go, React, Redis, Docker.\nData is from San Francisco's food truck open dataset.\n\n## Features\nAs a user, when you go to the websites' home page, you can see a list of marker, \neach marker represents a food truck.\n\n![img.png](doc/images/home-page.png)\n\nWhen you click a marker, you can see the food truck's applicant, location, and food items.\n\n![img.png](doc/images/pop.png)\n\nIf you live in San Francisco, you want see the trucks near your location, \nyou can click 'Your Location' button, the map will be switched to your location\n\n![img.png](doc/images/your-location.png)\n\nAs a admin, you can search all trucks who are serving a type of food, e.g. taco\n\n![img.png](doc/images/cli.png)\n\n## Tech Stacks\n### Backend\n- Redis as in memory database\n  - redis str, get truck(marshalled as json) by ID\n  - redis geo, to search nearby trucks by latitude, longitude, and radius.\n  - redis zset, to search a list of trucks by food items it served.\n- Go  \n- Iris Web Framework\n\n### Frontend\n- React\n- react-leaflet for map related feature\n- swr for state management\n\n## Design pattern and best practice\n- *hexagonal architecture*\n\n![img_1.png](doc/images/hexagonal.png)\n\nThe core of backend is /backend/packages/services/facilitySvc.go, service layer doesn't depend on\nstorage layer, and doesn't depend on UI layer.   \nBoth Cli and Web can use facility service.\n\n- *Dependency Injection*\n\nfacilitySvc is not hardcoded depending on rdb package(my own Redis lib), so if I want\nchange storage to mysql or mongodb later, I can implement the interface\nand inject the implementation to service.\n\nThis also conform to Open/Close principle, the facilitySvc is open to extend functionality, \nbut close to code change\n\n- *Separation of Concern*\n\nWhen do frontend coding, I also tried to apply this principle, for the truck map frontend.\nI use 3 components to render map (Map, FacilityMaker, SwitchLocation), each component care about it's own job, improved readability.\n\n- *Modular and DRY - Don't repeat your self*\n\nI aimed to separate business logic from infrastructure code in our project. Taking the `facilitySvc` as an example:\neach facility can have multiple food items, and each food item can be associated with multiple facilities.\nThis relationship pertains to business logic. In contrast, connecting to Redis and marshalling objects to JSON strings are common infrastructure tasks.\n\nBy wrapping Redis operations into a standalone package, instead of embedding this code within the facility service,\nI made the codebase more modular and reusable. This separation improves maintainability and allows infrastructure code\nto be reused across different services without duplication.\n\n- *Template pattern*\n\nThere are a lot of boilerplate to start a web application, \nI put these code to /backend/packages/util/irisbase applying Template Pattern,\nso the main file(/backend/cmds/web/main) looks clean and straightforward.\n\n- *Error handling*  \n\nEach function annotate error detail (e.g. which line throws the error, the cause of the error).\nIn develop mode, the API returns error detail to help frontend user to locate the issue.\nIn production mode, the API just return an 500 error to hide technical detail\n\n- *Generic Programming to improve ability to reuse code*\n\nFor example, the parse function in /backend/packages/util/yaml.go demonstrates how to create a reusable utility for parsing \nYAML files into any specified type. By using generics, we can create a single, versatile function that works with \nany data structure, enhancing our ability to write clean, reusable, and maintainable code.\n```\nfunc parse[T any](t *T, filePath string) (err error) {\n\tvar f *os.File\n\tf, err = os.Open(filePath)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer func(f *os.File) {\n\t\terr = f.Close()\n\t}(f)\n\n\tif err = yaml.NewDecoder(f).Decode(t); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n ```\n\n\n## Implementations\n### Frontend\n#### Code Structure\n```\n--frontend/\n----src/\n------map/\n--------FacilityMarkers.tsx   # add markers to map\n--------Map.tsx               # map container\n--------SwitchLoaction.tsx    # switch to your location\n------models/\n------utils/\n------config.ts               # global configs\n----.env.development          # development enviroment virables\n```\n#### Api call and state management\nAll meaningful code resides in /frontend/src/map, code in models and utils is very simple. \nthe useSWR hook combine api call and state management, one single line of code save the trouble of useEffect hook. \n```\n    const {data: center} = useSWR(Config.APIHost + '/api/facilities/center', fetcher)\n```\n#### Environment variables\nThe frontend app might run in two mode \n##### Development Mode \nIn development mode (pnpm dev), I start two web server, \nhttp://localhost:8080 as backend  http://localhost:5173 as frontend, so the api endpoint is http://localhost:8080/api/***.\n  I put this dev environment settings to .env.development\n```\nVITE_REACT_APP_API_HOST='http://localhost:8080'\n```\n##### Production Mode\nIn production mode frontend and backend are served as single app(the distribution of frontend is copied to \n/backend/web, and served by backend web server). \nI can use relative path to call backend api /api/facilities. In production there won't be .env file, \nso the api host default to empty string''. \n\n##### config.js\nAll environment reading code are put into config.ts , ensure single source of truth.\n```\nexport const Config = {\n    APIHost : import.meta.env.VITE_REACT_APP_API_HOST || '',\n}\n```\n#### Map Related Features\nI tried google map API first, but it's not totally free, I don't want checkin API Key to repo. And I want \npeople can easily play with this app, so I followed this link https://medium.com/@ujjwaltiwari2/a-guide-to-using-openstreetmap-with-react-70932389b8b1 \nto use react-leaflet\n### Backend\n#### Code structure\n```\n--cmds\n----cli/          # entrance of cli\n----web/          # entrance of web\n--packages\n----controllers/  # endpoint of APIs\n----models/      \n----services/     # implement business logic of food trucks\n----utils/        # infrastrcutres\n--web/            # put frontend distribution here\n```\n#### Seed Data\nIn packages/services/facilitySvc Seed() function, it read configs/data.csv, and parse it as Facility array,\nthen populate the data to redis.\n\n### Endpoints\n- */api/facilities/center*  Get the center of all trucks\n- */api/facilities?lat=\u0026lon=\u0026radius=* Get the facilities near the center with in the radius \n### Cli \n- share Facility Service with web, provides function of search facility by food items\n\n## Installation\nIf you don't have go, node, pnpm installed on you local machine, you can simply use docker compose to start the app.\n### Docker \nin the root directory of the project, run\n```shell\ndocker-compose up\n```\nWhen you see messages similar to below, then the app is up.\n```shell\nfood-truck-backend-1  | Now listening on:\nfood-truck-backend-1  | \u003e Network:  http://172.21.0.3:8080\nfood-truck-backend-1  | \u003e Local:    http://localhost:8080\nfood-truck-backend-1  | Application started. Press CTRL+C to shut down.\n```\n### Web\nUse your browser, go to http://localhost:8080 to see the food truck app.\n### Cli\nto run cli\n```shell\n# use docker ps to check docker container name\n⚡➜ ~ docker ps\nCONTAINER ID   IMAGE                COMMAND                  CREATED         STATUS         PORTS                    NAMES\n0f6039ec90d6   food-truck-backend   \"./main\"                 5 minutes ago   Up 5 minutes   0.0.0.0:8080-\u003e8080/tcp   food-truck-backend-1\n40f20e7696e4   redis:latest         \"docker-entrypoint.s…\"   5 minutes ago   Up 5 minutes   0.0.0.0:6379-\u003e6379/tcp   food-truck-redis-1\n\n# start a shell session\n⚡➜ ~ docker exec -it food-truck-backend-1 /bin/bash\n\n# in the shell session,  run ./food-cli\nroot@0f6039ec90d6:/go/src/app# ./food-cli\nLoad Config from  ./configs/cli.yaml\n\n# when you saw the 'Enter Food Item to search facility:', input a food item you want to find\nEnter Food Item to search facility: breakfast\nMunch A Bunch MISSION ST: 14TH ST to 15TH ST (1800 - 1899)\nMunch A Bunch BRYANT ST: ALAMEDA ST intersection\nMunch A Bunch FULTON ST: FRANKLIN ST to GOUGH ST (300 - 399)\nMunch A Bunch LARKIN ST: FERN ST to BUSH ST (1127 - 1199)\nMunch A Bunch 12TH ST: ISIS ST to BERNICE ST (332 - 365)\nMunch A Bunch 07TH ST: CLEVELAND ST to HARRISON ST (314 - 399)\nMunch A Bunch PARNASSUS AVE: HILLWAY AVE to 03RD AVE (400 - 599)\nMunch A Bunch 17TH ST: SAN BRUNO AVE to UTAH ST (2200 - 2299)\n```\n\n## Development\n### Spin up a redis server\n```shell\ndocker run --name food-redis -d -p 6379:6379 redis\n```\n### Start backend\ngo to /backend, \n```shell\ngo run backend/cmds/web/main.go\n```\nif you got the following error, it means backend can not connect to a host 'redis'\n```\npanic: facilitySvc.go:58 failed to cache facilities, dial tcp: lookup redis: no such host\n```\nyou can add 'redis' to you development machine's /etc/hosts file\n```\n127.0.0.1       localhost redis\n```\nor you can modify /backend/configs/web.yaml file, change the following line\n```\n  addr: redis:6379\n```\nto \n```\n  addr: localhost:6379\n```\n### Start CLI\n```\ngo run backend/cmds/cli/main.go\n```\nCli's config file is at /backend/configs/cli.yaml\n\n### Start Frontend\nyou need to install node, pnpm, then go to frontend/, run\n```\npnpm install\npnpm dev\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fformcms%2Ffood-truck","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fformcms%2Ffood-truck","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fformcms%2Ffood-truck/lists"}