Cointracker app with Golang and Vue3.js
April 11, 2022
This article is about creating a fullstack asset/portfolio tracker application using Golang for the backend, Vue3.js for the frontend with authentication handled by Firebase.
Let’s get started, this article is only for the backend setup.
For the backend, i took reference from crud app video as it was a simple and powerful set up with Golang, performant database with postgres + gorm for sql driver, Gorilla Mux for a quick out of the box setting up a backend router to build CRUD APIs. I also took inspiration for financial data structures from finance go.
These are the main project components are being used:
- Database - postgresql
- gorm
- Json marshall, unmarshall
- Project folder structure
- Gorilla Mux
- Cors
START
Setting up Postgres DB on Mac
create a go project
go mod init github.com/<your-github-user>/<name-of-project>
To start, initialise the project, it should create go.mod file
Set up project folder structure
- Cmd
- main.go
- pkg
- config
- app.go
- controllers
- transaction-controller.go
- models
- assets.go
- transaction.go
- db.go
- routes
- portfolioasets-routes.go
- utils
- utils.go
- config
API routes
Set up routes.
var RegisterPortfolioAssetsRoutes = func(r *mux.Router) {
// portfolio endpoint
r.HandleFunc("/assets", controllers.GetAssets).Methods("GET").Queries("uuid", "{uuid}")
// Txns endpoint
r.HandleFunc("/transactions", controllers.CreateTransaction).Methods("POST")
r.HandleFunc("/transactions/{transactionId}", controllers.UpdateTransaction).Methods("PUT")
r.HandleFunc("/transactions/{transactionId}", controllers.DeleteTransaction).Methods("DELETE")
}
Setup struct type
At this application’s core, the data model is the ‘Transaction’ struct where details such as type of transaction, name, price per coin, quantity, etc, are provided.
type Transaction struct {
gorm.Model
ID uint `gorm:"primaryKey" json:"-"`
Type string `json:"type"`
Symbol string `json:"symbol"`
Name string `json:"name"`
Quantity float64 `json:"quantity"`
Total float64 `json:"total"`
PricePerCoin float64 `json:"pricePerCoin"`
DateTimeTxn time.Time `json:"dateTimeTxn"`
Fees float64 `json:"fees"`
UserUUID string `json:"-"`
}
The frontend should receive a json object of ‘User Assets’ to display upon. This would consist namely of multiple Asset types owned by the user (i,e BTC, ETH, LUNA) and total asset value. Each asset type would be a collection of transactions for the particular asset type.
type Portfolio struct {
TotalAssetsBalance float64 `json:"totalAssetsBalance"`
UserId string `json:"userid"`
Assets map[string]Asset `json:"assets"`
AssetCount int `json:"-"`
}
type AssetTransaction struct {
TxnId uint `json:"txnId"`
Type string `json:"type"`
Symbol string `json:"symbol"`
Name string `json:"name"`
Quantity float64 `json:"quantity"`
Total float64 `json:"total"`
PricePerCoin float64 `json:"pricePerCoin"`
DateTimeTxn time.Time `json:"dateTimeTxn"`
Fees float64 `json:"fees"`
}
type Asset struct {
TotalValue float64 `json:"totalValue"`
TotalHolding float64 `json:"totalHolding"`
AveragePrice float64 `json:"averagePrice"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Transactions []AssetTransaction `json:"transactions"`
}
Set up controllers. On the controller methods, it would mainly be handling HTTP request and response. It would call upon the model to process and handle the database CRUD logic.
Set up Model methods.
This section consists of retrieving from PostGres DB all transactions from ‘userId’. Thereafter mapping each unique asset type to its relevant transaction records. Other values such as total holdings, average price and total value will be calculated and declared onto the data object.
func GetUserAssets(userId string) (Portfolio, error) {
log.Info("GetUserAssets", zap.String("userId", userId))
var portfolio Portfolio
portfolio.Assets = make(map[string]Asset)
portfolio.UserId = userId
// Get all transcations for userId
txns, db := getTransactionsByUserId(strings.ToLower(userId))
if db.Error != nil {
return portfolio, db.Error
}
for _, txn := range txns {
if a, found := portfolio.Assets[txn.Symbol]; !found {
// var asset Assetass
asset := Asset{
Name: txn.Name,
Symbol: txn.Symbol,
}
asset.Transactions = append(asset.Transactions, txn)
portfolio.Assets[txn.Symbol] = asset
} else {
a.Transactions = append(a.Transactions, txn)
portfolio.Assets[txn.Symbol] = a
}
}
for sym, asset := range portfolio.Assets {
var totalVal, totalHoldings float64
for _, t := range asset.Transactions {
totalVal = totalVal + t.Total
totalHoldings = totalHoldings + t.Quantity
}
asset.TotalValue = totalVal
asset.TotalHolding = totalHoldings
asset.AveragePrice = asset.TotalValue / asset.TotalHolding
portfolio.Assets[sym] = asset
portfolio.TotalAssetsBalance = portfolio.TotalAssetsBalance + asset.TotalValue
}
portfolio.AssetCount = len(portfolio.Assets)
return portfolio, nil
}
Testing on postman
{
"ETH": {
"totalValue": 3000.0,
"totalHolding": 1,
"averagePrice": 3100,
"holdings": [
{
"type": "B",
"pricePerCoin": 3100.0,
"totalSpent": 1500.0,
"fees": 0.0,
"quantity": 0.5,
"dateTimeTxn": "2022-03-31T02:38:27.927783+08:00"
},
{
"type": "B",
"pricePerCoin": 3100.0,
"totalSpent": 1500.0,
"fees": 0.0,
"quantity": 0.5,
"dateTimeTxn": "2022-03-31T02:38:27.927783+08:00"
}
]
},
"FTM": {
"totalValue": 1200.0,
"totalHolding": 650.0,
"averagePrice": 1.846,
"holdings": [
{
"type": "B",
"pricePerCoin": 3100.0,
"totalSpent": 1500.0,
"fees": 0.0,
"quantity": 0.5,
"dateTimeTxn": "2022-03-31T02:38:27.927783+08:00"
}
]
}
}
Formatting date-time should always be in ISO8601 format for ease of usage through the application.
This covers the backend setup to retrieve, create, update and delete transactions. I will be creating the frontend in Vue. Do stay tuned!
Postgres CHEAT SHEET:
https://gist.github.com/Kartones/dd3ff5ec5ea238d4c546
references:
- (start go) https://www.wolfe.id.au/2020/03/10/starting-a-go-project/
- (crud app) https://www.youtube.com/watch?v=jFfo23yIWac&t=68s
- (crud app) https://github.com/AkhilSharma90/Golang-MySQL-CRUD-Bookstore-Management-API/tree/master/pkg/config
- (finance go) https://golangdocs.com/golang-finance-go-package-stock-quote-options-chart
- (logging) https://www.honeybadger.io/blog/golang-logging/