MVC is a software architecture pattern which separates the application in three layers: Model, View and Controller. Model holds the business logic and interacts with the database, View is the layer which interacts with the users and the Controller handles the comunication between Model and View.
Wheel is MVC architecture based. But the Controller layer is called of Handler.
The models on Wheel are inside each resource directory, which is under directory app. It means if your application has a resource called person the model is in the directory:
app/person/person_model.go
Quick tip!
All models have the same suffix: _model
Just like models, views on Wheel are inside each resource directory, which is under directory app. It means if your application has a resource called person the view is in the directory:
app/person/person_view.go
Quick tip!
All views have the same suffix: _view
Wheel calls controllers as handlers. All handlers are together within the same directory app/handlers.
app/handlers/person_handler.go
Quick tip!
All handlers have the same suffix: _handler
Wheel is based in resources. By default, each resource has its own create, read, update and destroy routes identified by an unique pair of URL and HTTP method. For example, the default routes of the resource person are:
router.HandleFunc("/people", handlers.PersonList).Methods("GET")
router.HandleFunc("/people/{id}", handlers.PersonShow).Methods("GET")
router.HandleFunc("/people", handlers.PersonCreate).Methods("POST")
router.HandleFunc("/people/{id}", handlers.PersonUpdate).Methods("PUT")
router.HandleFunc("/people/{id}", handlers.PersonDestroy).Methods("DELETE")
Quick tip!
See https://restfulapi.net/ for mode details about REST.
JSON Web Tokens work as credentials which grant access to resources.
Wheel by default generates an unique JWT for each successful sing in request. See more details on Sessions Default Resources section.
When you create a new project with Wheel, it generates a pair of key files. One of them is the private key and it is used to generate the JWT while siging in and the other is used to validate the JWT and grant application access for each subsequent requests. See both keys in directory config/keys/.
You can generate by yourself the pair of key files. Just follow the example below:
$> cd /path/to/app/config/keys
$> openssl genrsa -out app.key.rsa 2048
$> openssl rsa -in app.key.rsa -pubout > app.key.rsa.pub
Quick tip!
See https://jwt.io for mode details about JWT.
Wheel has a built-in Session Control. Check all functionalities below:
Item | Definition |
---|---|
Sing up | Sign up function for internet public audience. |
Sign in | Authenticates users with valid credentials. Returns a token (JWT) for use in subsequent requests. |
Authorization | After successful authentication, authorizes (or not) requests with a valid token. |
Sign Out | Signed in users can sign out. |
Password Recovery | Nevermind if any user forgets password. Wheel sends internationalized email for password recovery and manages the entire process. |
Two Quick tips!
Middleware holds the functions that are executed before requests reach the handler.
Wheel generates by default two middlewares:
Authorization defines if requests from users has or hasn't permission to be executed. By default, Wheel defines three user roles: public, signed_in and admin.
Item | Definition |
---|---|
public | Internet public audience. |
signed_in | regular signed in users. |
admin | administrators/managers of the application. |
The whole authorization proccess is executed in a middleware and it has two steps. First step is to identify the role of the current user. Second is to check permissions. The method authorizeMiddleware in routes/middleware.go identifies the user role and the file routes/authorize.go has permissions rules.
Quick tip!
Check the authorization example in Getting Started section.
Users management are resources only allowed for admin users. They can:
Quick tip!
Check the Users item in Default Resources section.
Wheel uses the GORM which helps to interact with the database.
Migration is a feature of GORM. Because of it, you don't need to write SQL to change the database schema.
Wheel creates the file db/schema/migrate.go when generates a new project. The default generated migration creates the user's table, the admin user, the session's table and theirs relationship.
package schema
import (
"github.com/adilsonchacon/car_store/app/user"
"github.com/adilsonchacon/car_store/commons/app/model"
"github.com/adilsonchacon/car_store/commons/crypto"
"github.com/adilsonchacon/car_store/db/entities"
)
func Migrate() {
model.Db.AutoMigrate(&entities.User{})
_, err := user.FindByEmail("user@example.com")
if err != nil {
model.Db.Create(&entities.User{Name: "User Name", Email: "user@example.com", Password: crypto.SetPassword("!Secret.123!"), Locale: "en", Admin: true})
}
model.Db.AutoMigrate(&entities.Session{})
model.Db.Model(&entities.Session{}).AddForeignKey("user_id", "users(id)", "NO ACTION", "NO ACTION")
}
Wheel updates the file db/schema/migrate.go when you create a new CRUD.
$> wheel generate scaffold person name:string email:string birth_day:datetime
It will add to db/schema/migrate.go file the following line:
...
model.Db.AutoMigrate(&entities.Person{})
}
Quick tip!
Click here to check GORM migration documentation.
The Search Engine is a powerful filter for listing data. You can filter by one or more columns of a table in the database and specify the criteria. In the request URL, you need to set the search paramenter as a hash. The key of each element in hash is composed by the name of the column and the criterion separated by an underline "_".
The following two examples we are assuming a database table named people and it has the columns name, email and birth_day.
First example: listing people whose email is equals to "user@example.com"
$> curl -X GET http://localhost:8081/people?search[email_eq]=user@example.com
It will run the following SQL:
SELECT * FROM people WHERE email = "user@example.com"
Second example: listing people whose emails are in a list.
$> curl -X GET http://localhost:8081/people?search[email_in]=user@example.com,test@domain.com
It will run the following SQL:
SELECT * FROM people WHERE email IN ("user@example.com", "test@domain.com")
Third example: listing people whose name contains the letter "a" and were born after the year 1999.
$> curl -X GET http://localhost:8081/people?search[name_cont]=a&search[birth_day_gt]=1999-12-31
It will run the following SQL:
SELECT * FROM people WHERE name ILIKE "%a%" AND birth_day > '1999-12-31'
All the availables criteria you can find below:
Criterion | Mean | SQL |
---|---|---|
eq | is equal to | SELECT * FROM [TABLE] WHERE [COLUMN] = '[VALUE]' |
noteq | is not equal to | SELECT * FROM [TABLE] WHERE [COLUMN] <> '[VALUE]' |
cont | contains | SELECT * FROM [TABLE] WHERE [COLUMN] ILIKE '%[VALUE]%' |
notcont | does not contain | SELECT * FROM [TABLE] WHERE [COLUMN] NOT ILIKE '%[VALUE]%' |
start | starts with | SELECT * FROM [TABLE] WHERE [COLUMN] ILIKE '[VALUE]%' |
notstart | does not start with | SELECT * FROM [TABLE] WHERE [COLUMN] NOT ILIKE '[VALUE]%' |
end | ends with | SELECT * FROM [TABLE] WHERE [COLUMN] ILIKE '%[VALUE]' |
notend | does not end with | SELECT * FROM [TABLE] WHERE [COLUMN] NOT ILIKE '%[VALUE]' |
in | includes | SELECT * FROM [TABLE] WHERE [COLUMN] IN ([VALUE_1], [VALUE_2], ... , [VALUE_N]) |
notin | does not include | SELECT * FROM [TABLE] WHERE [COLUMN] NOT IN ([VALUE_1], [VALUE_2], ... , [VALUE_N]) |
gt | great than | SELECT * FROM [TABLE] WHERE [COLUMN] > [VALUE] |
gteq | great than or equal | SELECT * FROM [TABLE] WHERE [COLUMN] >= [VALUE] |
lt | less than | SELECT * FROM [TABLE] WHERE [COLUMN] < [VALUE] |
lteq | less than or equal | SELECT * FROM [TABLE] WHERE [COLUMN] <= [VALUE] |
null | is null | SELECT * FROM [TABLE] WHERE [COLUMN] IS NULL |
notnull | is not null | SELECT * FROM [TABLE] WHERE [COLUMN] IS NOT NULL |
true | is true | SELECT * FROM [TABLE] WHERE [COLUMN] = 't' |
false | is false | SELECT * FROM [TABLE] WHERE [COLUMN] = 'f' |
Just like Search Engine, Pagination is a feature for listing data.
Pagination has two parameters:
Parameter | Definition | Default |
---|---|---|
page | Page you want to see | Default value is 1 |
per_page | Amount of entries per page | Default value is 20 |
Example:
$> curl -X GET http://localhost:8081/people?page=2&per_page=10
Returns something like:
{
"pagination": {
"current_page": 2,
"total_pages": 10,
"total_entries": 96
},
"movies": [
...
]
}
Quick tips!
Pagination, Search Engine and Ordering (item below) can work together.
Check the Pagination item in Internals section for details.
Ordering is also a feature for listing data and can work together with Search Engine and Pagination. You can order by any SQL command.
Example:
$> curl -X GET http://localhost:8081/people?order=birth_day%20DESC
Very important!
The "%20" represents a space char for URL encode.
Because of secure reasons, values for ordering are whitelisted. Each handler has, by default, its attributes (correspodent table's columns in the database) whitelisted.
Quick tips!
Pagination, Search Engine and Ordering can work together.
Check the Ordering item in Internals section for details.
Wheel has support for sending emails. There are two scenarios where it happens:
The short example to send emails is:
import /app/path/commons/mailer
...
mailer.AddTo("Recipient Name", "recipient@email.com")
subject := "Hello world"
body := "This is a test email message"
go mailer.Send(subject, body, true)
Quick tip!
Check the Mailer item in Internals section.
In previous item you learned how to use Wheel's internals to send emails. You can use internationalization to send emails using the recipient user's language.
Very important!
To set the user's language is necessary to update the locale column.
The current version of Wheel has the following locales available.
To add a new locale to your project, get the file config/locales/en.yml as example, translate the words/phrases and save as "the locale code" plus .yml. For example, if you want to translate to french save the new locale file as config/locales/fr.yml. After, edit the config/app.yml and add to option locale the new locale code.
You can log the application events for auditing or debuging. Logs are showed in stdout and persisted in file log/app.log.
import /app/path/commons/log
...
log.Info.Println("Hello World!")
The function Info from package log is nothing but a classification for the event. Events classes are: