DartCounter2.0 back-end with GoLang front-end with Angular Project contains of two parts:
- Go (API) back-end
- Angular front-end
===
( Placeholder since we need to show the flowcharts after v1.0.0 is released )
=== GO ===
-
Gorilla Mux | 'go get -u github.com/gorilla/mux.'
-
air | *Having some trouble getting air to work... (WIP) 'go get -u github.com/cosmtrek/air'
-
CORS (Cross-Origin Resource Sharing)
is a mechanism that allows or restricts requested resources on a web server depending on where the HTTP request was initiated. This is used for security reasons and is particularly important when your front-end and back-end are served from different origins (e.g., different ports during development).
By enabling CORS in your Go server, you're allowing your Angular app (running on a different port) to make requests to your Go API.
cmd/dartcounter/main.go: The entry point of your application. pkg/game/game.go: Contains the Game struct and related methods. pkg/player/player.go: Defines the Player struct. pkg/score/score.go: Manages the Score struct. go.mod: Manages your project's dependencies.
func (m MyInt) IsPositive() bool {
return m > 0
}
func main() {
var i MyInt = 5
fmt.Println(i.IsPositive()) // Output: true
}
The method takes a receiver parameter m of type MyInt, which is the instance of the type that the method is called on.
In Go, := is for declaration + assignment, whereas = is for assignment only. For example, var foo int = 10 is the same as foo := 10.
- Pointers and the * Symbol
When you see *Game, it means a pointer to a Game instance, allowing you to modify the original Game instance passed around.
Go does not use the this keyword like TypeScript or JavaScript. Instead, Go methods are defined with a receiver argument, which serves a similar purpose. Interfaces in Go are a way to specify a set of method signatures. Any type that implements those methods satisfies the interface. This is different from languages with class-based inheritance.
After creating these files, you can build and run your application using the Go command line:
Navigate to the dartCounter directory. Run 'go build ./cmd/dartcounter' to build your application. Execute ./dartcounter to run the application.
Package Management: Use go mod init to initialize a new module. This will create a go.mod file which will manage your dependencies.
PlantUml makes the design of classes & interfaces visible.
(Note, this was working, need to troubleshoot to make this thing work again dynamically)
https://www.baeldung.com/cs/program-to-interface
-
Interface Definition: The ScoringSystem interface declares methods like UpdateScore and CheckWinCondition. These methods are essential for updating a player's score and checking the win condition of the game.
-
StandardScoring Implementation: The StandardScoring class is a concrete implementation of the ScoringSystem interface. It provides specific logic for updating scores based on dart throws and determining the game's win condition.
-
Flexibility: By using an interface, the DartCounter application can easily adapt to different scoring rules without modifying the core game logic. New scoring systems can be implemented and integrated seamlessly.
-
Maintainability: This approach enhances the maintainability of the code. Changes in the scoring logic only require modifications in the scoring system implementations, not in the Game class or elsewhere.
Benefits of Using the Interface Flexibility: The game logic is not tied to a specific scoring implementation. If you decide to introduce a new scoring system (e.g., a variant of dart scoring rules), you can simply create a new struct that implements the ScoringSystem interface.
Maintainability: Changes to the scoring logic can be made in the specific implementations of the ScoringSystem interface, without affecting the Game struct or other parts of the application.
Testability: It's easier to test the Game logic by mocking different scoring systems.
- Trunk.yaml (To Investigate):
https://docs.trunk.io/check/reference/trunk-yaml
- pre-commit (to be implemented):
- Go https://marketplace.visualstudio.com/items?itemName=golang.Go
- REST Client https://marketplace.visualstudio.com/items?itemName=humao.rest-client
- ESLint https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint
=== Angular ===
Install angular and create project
npm install -g @angular/cli
ng new dart-counter-frontend --no-standalone --routing --ssr=false #
cd dart-counter-frontend
Some issue's regarding app.module.ts and routing, please read: angular/angular#52761 angular/angular#52751
Testing:
ng server
ng generate module app-routing --flat --module=app
Generate components and service
ng generate component <component>
ng generate service game
Linting
# Install ESLint extension
ng lint
CORS (Cross-Origin Resource Sharing) is a security feature implemented in web browsers. It restricts web applications from making requests to a domain different from the one that served the web page, unless the server on the other end explicitly allows it. This is particularly important for applications like yours, where the Angular front-end (served from http://localhost:4200) makes requests to a Go API server (running on http://localhost:8080).
When you make certain types of HTTP requests (like POST, PUT, DELETE, or any request that includes custom headers) from a web page to a different domain, the browser first sends an OPTIONS request before the actual request. This is known as a "preflight" request. The purpose is to check whether the actual request is safe to send, based on the CORS policy of the server.
The OPTIONS request asks the server for the permissions (or "CORS policy") it has regarding requests from other origins. The server responds with headers that indicate whether the actual request is allowed. These headers include:
Access-Control-Allow-Origin: Specifies which origins are allowed. A value of * means any origin is allowed. Access-Control-Allow-Methods: Lists the HTTP methods that are allowed. Access-Control-Allow-Headers: Indicates which headers can be used in the actual request.
The Angular application, when tried to make a POST request to the Go API, the browser first sent an OPTIONS request as a preflight check. However, the Go server was not configured to handle OPTIONS requests correctly for the /game/start and /game/score endpoints. As a result, the server responded with a 405 Method Not Allowed status, causing the browser to block the subsequent POST request due to the CORS policy violation.
npx create-react-app frontend-react --template typescript
-
Using TypeScript in your React project offers several benefits, such as improved code quality and developer experience due to static typing. Here are some key points about using TypeScript with React:
-
TypeScript Interfaces: TypeScript interfaces are a powerful way to define the shape of objects or props in React. They help ensure that components receive and use props correctly.
-
Component Props and State: Define interfaces for your component props and state for better type checking.
-
Event Handling: TypeScript can help define the types for event objects in your event handlers, making it easier to access event properties safely.
-
Integration with Third-Party Libraries: Many popular libraries have TypeScript type definitions available, either bundled with the library or as separate @types/ packages.
We need to prepare Dockerfiles for each project, GO as backend and React as frontend:
--- backend go ---
touch backend-go/Dockerfile
This Dockerfile uses the official Go image, sets up the working directory, copies your Go files, installs dependencies, builds your application, and sets the default command to run your app.
cd backend-go
docker build -t dartcounter api .
--- frontend react ---
This Dockerfile first builds your React application using Node.js and then sets up an Nginx server to serve the built files.
cd frontend-react
docker build -t frontend-react .
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d65c8fc3f6ec frontend-react "/docker-entrypoint.…" 8 seconds ago Up 7 seconds 0.0.0.0:80->80/tcp wonderful_haslett
6019fde346ea dartcounterapi "./main" About a minute ago Up About a minute 0.0.0.0:8080->8080/tcp boring_jang
- Install Heroku CLI
- Currently two application are in Heroku:
- backend-dartapp
- frontend-dartapp
This will double the cost.
- Ideally we can implement nginx for routes and make the back-end available in the same app to save dyno's
Building and pushing to Heroku Container Registry
heroku login
heroku create dartcounter
heroku container:login # Logs in at the Heroku Container Registry
heroku apps # shows apps
# go backend
cd path/to/your/go/project
docker build -t registry.heroku.com/dartcounterapi/api .
docker push registry.heroku.com/dartcounterapi/api
heroku container:release api -a dartcounterapi
===
cd path/to/your/react/project
docker build -t registry.heroku.com/dartcounterapi/web .
docker push registry.heroku.com/dartcounterapi/web
heroku container:release web -a dartcounterapi
---
heroku open -a dartcounterapi
# stop
heroku ps:scale web=0 -a dartcounterapi
heroku ps:scale api=0 -a dartcounterapi
# Open application
heroku open -a dartcounterapi
(NOTE: Tradional deployment are possible using GIT) If you're deploying a traditional Heroku app (not using containers), you can deploy your app directly from your Git repository:
git push heroku master
This command pushes your code to the Heroku remote, triggering a build and deployment.
heroku ps -a dartcounterapi # Verify dynos
scaling
heroku ps:scale web=1 -a dartcounterapi
heroku ps:scale api=1 -a dartcounterapi
The error bind() to 0.0.0.0:80 failed (13: Permission denied) suggests that the Nginx server inside your Docker container is trying to bind to port 80, which is not allowed on Heroku. Heroku dynamically assigns a port and exposes it through the $PORT environment variable. Your application must listen on this port.
For local testing we need docker compose. It contains following container applications:
- backend
- frontend
- nginx
docker-compose up --build # will start the containers (and dependencies)
docker-compose down # will close all containers.