Skip to content

Commit b071731

Browse files
authored
Merge pull request #258 from mknycha/add-tutorial-for-getting-started
Add getting started docs and Postgres tutorial
2 parents 6c7d312 + d3ec78d commit b071731

File tree

4 files changed

+207
-0
lines changed

4 files changed

+207
-0
lines changed

FAQ.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,9 @@
6868
Database-specific locking features are used by *some* database drivers to prevent multiple instances of migrate from running migrations at the same time
6969
the same database at the same time. For example, the MySQL driver uses the `GET_LOCK` function, while the Postgres driver uses
7070
the `pg_advisory_lock` function.
71+
72+
#### Do I need to create a table for tracking migration version used?
73+
No, it is done automatically.
74+
75+
#### Can I use migrate with a non-Go project?
76+
Yes, you can use the migrate CLI in a non-Go project, but there are probably other libraries/frameworks available that offer better test and deploy integrations in that language/framework.

GETTING_STARTED.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Getting started
2+
Before you start, you should understand the concept of forward/up and reverse/down database migrations.
3+
4+
Configure a database for your application. Make sure that your database driver is supported [here](README.md#databases)
5+
6+
## Create migrations
7+
Create some migrations using migrate CLI. Here is an example:
8+
```
9+
migrate create -ext sql -dir db/migrations -seq create_users_table
10+
```
11+
Once you create your files, you should fill them.
12+
13+
**IMPORTANT:** In a project developed by more than one person there is a chance of migrations inconsistency - e.g. two developers can create conflicting migrations, and the developer that created his migration later gets it merged to the repository first.
14+
Developers and Teams should keep an eye on such cases (especially during code review).
15+
[Here](https://github.com/golang-migrate/migrate/issues/179#issuecomment-475821264) is the issue summary if you would like to read more.
16+
17+
Consider making your migrations idempotent - we can run the same sql code twice in a row with the same result. This makes our migrations more robust. On the other hand, it causes slightly less control over database schema - e.g. let's say you forgot to drop the table in down migration. You run down migration - the table is still there. When you run up migration again - `CREATE TABLE` would return an error, helping you find an issue in down migration, while `CREATE TABLE IF NOT EXISTS` would not. Use those conditions wisely.
18+
19+
In case you would like to run several commands/queries in one migration, you should wrap them in a transaction (if your database supports it).
20+
This way if one of commands fails, our database will remain unchanged.
21+
22+
## Run migrations
23+
Run your migrations through the CLI or your app and check if they applied expected changes.
24+
Just to give you an idea:
25+
```
26+
migrate -database YOUR_DATBASE_URL -path PATH_TO_YOUR_MIGRATIONS up
27+
```
28+
29+
Just add the code to your app and you're ready to go!
30+
31+
Before commiting your migrations you should run your migrations up, down, and then up again to see if migrations are working properly both ways.
32+
(e.g. if you created a table in a migration but reverse migration did not delete it, you will encounter an error when running the forward migration again)
33+
It's also worth checking your migrations in a separate, containerized environment. You can find some tools in the end of this document.
34+
35+
**IMPORTANT:** If you would like to run multiple instances of your app on different machines be sure to use a database that supports locking when running migrations. Otherwise you may encounter issues.
36+
37+
## Further reading:
38+
- [PostgreSQL tutorial](database/postgres/TUTORIAL.md)
39+
- [Best practices](MIGRATIONS.md)
40+
- [FAQ](FAQ.md)
41+
- Tools for testing your migrations in a container:
42+
- https://github.com/dhui/dktest
43+
- https://github.com/ory/dockertest

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,16 @@ func main() {
140140
}
141141
```
142142

143+
## Getting started
144+
145+
Go to [getting started](GETTING_STARTED.md)
146+
147+
## Tutorials
148+
149+
- [PostgreSQL](database/postgres/TUTORIAL.md)
150+
151+
(more tutorials to come)
152+
143153
## Migration files
144154

145155
Each migration has an up and down migration. [Why?](FAQ.md#why-two-separate-files-up-and-down-for-a-migration)

database/postgres/TUTORIAL.md

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# PostgreSQL tutorial for beginners
2+
3+
## Create/configure database
4+
5+
For the purpose of this tutorial let's create PostgreSQL database called `example`.
6+
Our user here is `postgres`, password `password`, and host is `localhost`.
7+
```
8+
psql -h localhost -U postgres -w -c "create database example;"
9+
```
10+
When using Migrate CLI we need to pass to database URL. Let's export it to a variable for convienience:
11+
```
12+
export POSTGRESQL_URL=postgres://postgres:password@localhost:5432/example?sslmode=disable
13+
```
14+
`sslmode=disable` means that the connection with our database will not be encrypted. Enabling it is left as an exercise.
15+
16+
You can find further description of database URLs [here](README.md#database-urls).
17+
18+
## Create migrations
19+
Let's create table called `users`:
20+
```
21+
migrate create -ext sql -dir db/migrations -seq create_users_table
22+
```
23+
If there were no errors, we should have two files available under `db/migrations` folder:
24+
- 000001_create_users_table.down.sql
25+
- 000001_create_users_table.up.sql
26+
27+
Note the `sql` extension that we provided.
28+
29+
In the `.up.sql` file let's create the table:
30+
```
31+
CREATE TABLE IF NOT EXISTS users(
32+
user_id serial PRIMARY KEY,
33+
username VARCHAR (50) UNIQUE NOT NULL,
34+
password VARCHAR (50) NOT NULL,
35+
email VARCHAR (300) UNIQUE NOT NULL
36+
);
37+
```
38+
And in the `.down.sql` let's delete it:
39+
```
40+
DROP TABLE IF EXISTS users;
41+
```
42+
By adding `IF EXISTS/IF NOT EXISTS` we are making migrations idempotent - you can read more about idempotency in [getting started](GETTING_STARTED.md#create-migrations)
43+
44+
## Run migrations
45+
```
46+
migrate -database ${POSTGRESQL_URL} -path db/migrations up
47+
```
48+
Let's check if the table was created properly by running `psql example -c "\d users"`.
49+
The output you are supposed to see:
50+
```
51+
Table "public.users"
52+
Column | Type | Modifiers
53+
----------+------------------------+---------------------------------------------------------
54+
user_id | integer | not null default nextval('users_user_id_seq'::regclass)
55+
username | character varying(50) | not null
56+
password | character varying(50) | not null
57+
email | character varying(300) | not null
58+
Indexes:
59+
"users_pkey" PRIMARY KEY, btree (user_id)
60+
"users_email_key" UNIQUE CONSTRAINT, btree (email)
61+
"users_username_key" UNIQUE CONSTRAINT, btree (username)
62+
```
63+
Great! Now let's check if running reverse migration also works:
64+
```
65+
migrate -database ${POSTGRESQL_URL} -path db/migrations down
66+
```
67+
Make sure to check if your database changed as expected in this case as well.
68+
69+
## Database transactions
70+
71+
To show database transactions usage, let's create another set of migrations by running:
72+
```
73+
migrate create -ext sql -dir db/migrations -seq add_mood_to_users
74+
```
75+
Again, it should create for us two migrations files:
76+
- 000002_add_mood_to_users.down.sql
77+
- 000002_add_mood_to_users.up.sql
78+
79+
In Postgres, when we want our queries to be done in a transaction, we need to wrap it with `BEGIN` and `COMMIT` commands.
80+
In our example, we are going to add a column to our database that can only accept enumerable values or NULL.
81+
Migration up:
82+
```
83+
BEGIN;
84+
85+
CREATE TYPE enum_mood AS ENUM (
86+
'happy',
87+
'sad',
88+
'neutral'
89+
);
90+
ALTER TABLE users ADD COLUMN mood enum_mood;
91+
92+
COMMIT;
93+
```
94+
Migration down:
95+
```
96+
BEGIN;
97+
98+
ALTER TABLE users DROP COLUMN mood;
99+
DROP TYPE enum_mood;
100+
101+
COMMIT;
102+
```
103+
104+
Now we can run our new migration and check the database:
105+
```
106+
migrate -database ${POSTGRESQL_URL} -path db/migrations up
107+
psql example -c "\d users"
108+
```
109+
Expected output:
110+
```
111+
Table "public.users"
112+
Column | Type | Modifiers
113+
----------+------------------------+---------------------------------------------------------
114+
user_id | integer | not null default nextval('users_user_id_seq'::regclass)
115+
username | character varying(50) | not null
116+
password | character varying(50) | not null
117+
email | character varying(300) | not null
118+
mood | enum_mood |
119+
Indexes:
120+
"users_pkey" PRIMARY KEY, btree (user_id)
121+
"users_email_key" UNIQUE CONSTRAINT, btree (email)
122+
"users_username_key" UNIQUE CONSTRAINT, btree (username)
123+
```
124+
125+
## Optional: Run migrations within your Go app
126+
Here is a very simple app running migrations for the above configuration:
127+
```
128+
import (
129+
"log"
130+
131+
"github.com/golang-migrate/migrate/v4"
132+
_ "github.com/golang-migrate/migrate/v4/database/postgres"
133+
_ "github.com/golang-migrate/migrate/v4/source/file"
134+
)
135+
136+
func main() {
137+
m, err := migrate.New(
138+
"file://db/migrations",
139+
"postgres://postgres:postgres@localhost:5432/example?sslmode=disable")
140+
if err != nil {
141+
log.Fatal(err)
142+
}
143+
if err := m.Up(); err != nil {
144+
log.Fatal(err)
145+
}
146+
}
147+
```
148+
You can find details [here](README.md#use-in-your-go-project)

0 commit comments

Comments
 (0)