flecks/website/docs/database.mdx

549 lines
14 KiB
Plaintext
Raw Normal View History

2024-01-02 15:13:43 -06:00
---
title: Database
2024-01-03 16:14:01 -06:00
description: Define models and connect to a database.
2024-01-02 15:13:43 -06:00
---
import Create from '@site/helpers/create';
import InstallPackage from '@site/helpers/install-package';
# Database
flecks provides database connection through [Sequelize](https://sequelize.org/) and database
2024-01-03 16:14:01 -06:00
server instances through either flat SQLite databases or [Docker](https://www.docker.com/)-ized
2024-01-02 15:13:43 -06:00
database servers.
## Install and configure
We'll start from scratch as an example. Create a new flecks application:
<Create pkg="db_test" type="app" />
2024-01-04 03:23:04 -06:00
Now in your new application directory, add `@flecks/db`:
2024-01-02 15:13:43 -06:00
2024-01-04 03:23:04 -06:00
```bash
npx flecks add @flecks/db
2024-01-02 15:13:43 -06:00
```
Finally, `npm start` your application and you will see lines like the following in the logs:
```
@flecks/db/server/connection config: { dialect: 'sqlite', storage: ':memory:' } +0ms
@flecks/db/server/connection synchronizing... +107ms
@flecks/db/server/connection synchronized +2ms
```
By default, flecks will connect to an in-memory SQLite database to get you started instantly.
## Your models
Astute observers may have noticed a line preceding the ones earlier:
```
@flecks/core/flecks gathered '@flecks/db/server.models': [] +0ms
```
Let's create a fleck that makes a model so we can get a feel for how it works.
First, create a fleck in your application:
2024-01-05 13:47:03 -06:00
<Create pkg="content" type="fleck" />
2024-01-02 15:13:43 -06:00
2024-01-05 13:47:03 -06:00
Now, let's hop into `packages/content/src/index.js` and add a hook implementation:
2024-01-02 15:13:43 -06:00
2024-01-05 13:47:03 -06:00
```javascript title="packages/content/src/index.js"
2024-01-02 15:13:43 -06:00
export const hooks = {
'@flecks/db/server.models': (flecks) => {
const {Model, Types} = flecks.fleck('@flecks/db/server');
2024-01-05 13:47:03 -06:00
class Content extends Model {
2024-01-02 15:13:43 -06:00
static get attributes() {
return {
2024-01-05 13:47:03 -06:00
text: {
type: Types.TEXT,
allowNull: false,
},
};
}
};
return {
Content,
};
},
}
```
Now, `npm start` your application and you will see that line looks different:
```
@flecks/core/flecks gathered '@flecks/db/server.models': [ 'Content' ] +0ms
```
Our model is recognized!
Let's add one more model and create an association between them:
```javascript title="packages/content/src/index.js"
export const hooks = {
'@flecks/db/server.models': (flecks) => {
const {Model, Types} = flecks.fleck('@flecks/db/server');
class Content extends Model {
static get attributes() {
return {
text: {
type: Types.TEXT,
2024-01-02 15:13:43 -06:00
allowNull: false,
},
2024-01-05 13:47:03 -06:00
};
}
static associate({Tag}) {
this.hasMany(Tag);
}
};
class Tag extends Model {
static get attributes() {
return {
2024-01-02 15:13:43 -06:00
value: {
type: Types.STRING,
allowNull: false,
},
};
}
2024-01-05 13:47:03 -06:00
static associate({Content}) {
this.belongsTo(Content);
}
2024-01-02 15:13:43 -06:00
};
return {
2024-01-05 13:47:03 -06:00
Content,
Tag,
2024-01-02 15:13:43 -06:00
};
},
}
```
2024-01-06 21:07:06 -06:00
:::tip[Ess-Cue-Ell-ize]
2024-01-04 03:23:04 -06:00
`@flecks/db` uses [Sequelize](https://sequelize.org/) under the hood. You can dive into
[their documentation](https://sequelize.org/docs/v6/getting-started/) to learn even more.
2024-01-02 15:13:43 -06:00
2024-01-05 13:47:03 -06:00
:::
2024-01-02 15:13:43 -06:00
2024-01-05 13:47:03 -06:00
If you were to `npm start`, you would see the line:
2024-01-02 15:13:43 -06:00
```
2024-01-05 13:47:03 -06:00
@flecks/core/flecks gathered '@flecks/db/server.models': [ 'Content', 'Tag' ] +0ms
2024-01-02 15:13:43 -06:00
```
2024-01-05 13:47:03 -06:00
## Providing models with `Flecks.provide()`
2024-01-02 15:13:43 -06:00
2024-01-05 13:47:03 -06:00
When building Real:tm: applications we are usually going to need a bunch of models. If we add all
of them into that one single file, things are going to start getting unwieldy. Let's create a
`src/models` directory in our `packages/content` fleck and add a `content.js` and `tag.js` source
files with the following code:
2024-01-02 15:13:43 -06:00
2024-01-05 13:47:03 -06:00
```javascript title="packages/content/src/models/content.js"
export default (flecks) => {
const {Model, Types} = flecks.fleck('@flecks/db/server');
return class Content extends Model {
2024-01-02 15:13:43 -06:00
2024-01-05 13:47:03 -06:00
static get attributes() {
return {
text: {
type: Types.TEXT,
allowNull: false,
},
};
}
2024-01-03 16:14:01 -06:00
2024-01-05 13:47:03 -06:00
static associate({Tag}) {
this.hasMany(Tag);
}
2024-01-03 16:14:01 -06:00
2024-01-05 13:47:03 -06:00
};
};
```
2024-01-03 16:14:01 -06:00
2024-01-05 13:47:03 -06:00
```javascript title="packages/content/src/models/tag.js"
2024-01-03 16:14:01 -06:00
export default (flecks) => {
const {Model, Types} = flecks.fleck('@flecks/db/server');
2024-01-05 13:47:03 -06:00
return class Tag extends Model {
2024-01-03 16:14:01 -06:00
static get attributes() {
return {
key: {
type: Types.STRING,
allowNull: false,
},
value: {
type: Types.STRING,
allowNull: false,
},
};
}
2024-01-05 13:47:03 -06:00
static associate({Content}) {
this.hasMany(Content);
}
2024-01-03 16:14:01 -06:00
};
2024-01-05 13:47:03 -06:00
};
2024-01-03 16:14:01 -06:00
```
2024-01-05 13:47:03 -06:00
Notice that this looks very similar to how we defined the models above, but this time we're only
returning the classes.
2024-01-03 16:14:01 -06:00
2024-01-05 13:47:03 -06:00
Now, hop over to `packages/content/src/index.js` and let's rewrite the hook implementation:
```javascript title="packages/content/src/index.js"
import {Flecks} from '@flecks/core/server';
2024-01-03 16:14:01 -06:00
export const hooks = {
'@flecks/db/server.models': Flecks.provide(require.context('./models')),
}
```
We're passing the path to our models directory to `require.context` which is then passed to
`Flecks.provide`. This is completely equivalent to our original code, but now we can add more
2024-01-05 13:47:03 -06:00
models by adding individual files in `packages/content/src/models` and keep things tidy.
2024-01-03 16:14:01 -06:00
2024-01-06 21:07:06 -06:00
:::info[Continue gathering knowledge]
2024-01-03 16:14:01 -06:00
2024-01-05 13:47:03 -06:00
For a more detailed treatment of gathering and providing in flecks, see
[the gathering guide](#todo).
2024-01-03 16:14:01 -06:00
:::
## Working with models
2024-01-05 13:47:03 -06:00
Let's do something with them. Edit `packages/content/src/index.js` again like
2024-01-02 15:13:43 -06:00
so:
2024-01-05 13:47:03 -06:00
```javascript title="packages/content/src/index.js"
import {Flecks} from '@flecks/core/server';
2024-01-02 15:13:43 -06:00
export const hooks = {
// highlight-start
'@flecks/server.up': async (flecks) => {
2024-01-05 22:51:28 -06:00
const {Content, Tag} = flecks.db.Models;
2024-01-05 13:47:03 -06:00
console.log(
'There were',
await Content.count(), 'pieces of content',
'and',
await Tag.count(), 'tags.',
);
2024-01-02 15:13:43 -06:00
},
// highlight-end
2024-01-03 16:14:01 -06:00
'@flecks/db/server.models': Flecks.provide(require.context('./models')),
2024-01-02 15:13:43 -06:00
}
```
2024-01-05 13:47:03 -06:00
We have to configure `build/flecks.yml` so that the database comes up before we try to use it:
```yml
'@db_test/content:./packages/content/src': {}
'@flecks/core':
id: db_test
'@flecks/db': {}
// highlight-start
'@flecks/server':
up:
- '@flecks/db'
- '@db_test/content'
// highlight-end
```
2024-01-02 15:13:43 -06:00
Now, another `npm start` will greet us with this line in the output:
```
2024-01-05 13:47:03 -06:00
There were 0 pieces of content and 0 tags.
2024-01-02 15:13:43 -06:00
```
2024-01-05 13:47:03 -06:00
Not very interesting. Let's add some, but only if there aren't any yet:
2024-01-02 15:13:43 -06:00
2024-01-05 13:47:03 -06:00
```javascript title="packages/content/src/index.js"
2024-01-02 15:13:43 -06:00
export const hooks = {
'@flecks/server.up': async (flecks) => {
2024-01-05 22:51:28 -06:00
const {Tag} = flecks.db.Models;
2024-01-05 13:47:03 -06:00
console.log(
'There were',
await Content.count(), 'pieces of content',
'and',
await Tag.count(), 'tags.',
);
2024-01-02 15:13:43 -06:00
// highlight-start
2024-01-05 13:47:03 -06:00
if (0 === await Content.count()) {
await Content.create(
{text: 'lorem ipsum', Tags: [{value: 'cool'}, {value: 'trending'}]},
{include: [Tag]}
);
await Content.create(
{text: 'blah blah', Tags: [{value: 'awesome'}]},
{include: [Tag]}
);
2024-01-02 15:13:43 -06:00
}
2024-01-05 13:47:03 -06:00
console.log(
'There are',
await Content.count(), 'pieces of content',
'and',
await Tag.count(), 'tags.',
);
2024-01-02 15:13:43 -06:00
// highlight-end
},
2024-01-03 16:14:01 -06:00
'@flecks/db/server.models': Flecks.provide(require.context('./models')),
2024-01-02 15:13:43 -06:00
}
```
2024-01-05 13:47:03 -06:00
Another `npm start` and we see:
```
There were 0 pieces of content and 0 tags.
There are 2 pieces of content and 3 tags.
```
Great!
2024-01-02 15:13:43 -06:00
## Persistence
You'll notice that if you run it again, it will always say
```
2024-01-05 13:47:03 -06:00
There were 0 pieces of content and 0 tags.
There are 2 pieces of content and 3 tags.
2024-01-02 15:13:43 -06:00
```
What's up with that? Remember in the beginning:
> By default, flecks will connect to an in-memory SQLite database to get you started instantly.
This means that the database will only persist as long as the life of your application. When you
2024-01-05 13:47:03 -06:00
restart it, you'll get a fresh new database every time. It was **quick to get started** developing,
but this isn't very helpful for any real purpose. Let's make a change to our `build/flecks.yml`:
2024-01-02 15:13:43 -06:00
2024-01-03 16:14:01 -06:00
```yml title="build/flecks.yml"
2024-01-05 13:47:03 -06:00
'@db_test/content:./packages/content/src': {}
'@flecks/core':
id: db_test
2024-01-02 15:13:43 -06:00
'@flecks/db': {}
// highlight-start
'@flecks/db/server':
database: './persistent.sql'
// highlight-end
'@flecks/server': {}
```
Now `npm start` again. You'll see our old familiar message:
```
2024-01-05 13:47:03 -06:00
There were 0 pieces of content and 0 tags.
There are 2 pieces of content and 3 tags.
2024-01-02 15:13:43 -06:00
```
This time though, our application wrote the SQLite database to disk at `./persistent.sql`. If we
give it one more go, we'll finally see what we expect:
```
2024-01-05 13:47:03 -06:00
There were 2 pieces of content and 3 tags.
There are 2 pieces of content and 3 tags.
2024-01-02 15:13:43 -06:00
```
A persistent database!
## Containerization
Sure, our database is persistent... kinda. That `persistent.sql` file is a bit of a kludge and
isn't much of a long-term (or production) solution. Let's remove it:
```
rm persistent.sql
```
Our small-core philosophy means that you don't pay for spinning up a database by default. However,
it's trivial to accomplish a *"real"* database connection if you have Docker installed on your
machine.
<details>
<summary>How do I know if I have Docker running on my machine?</summary>
A decent way to test if your machine is ready to continue with this guide is to run the following
command:
`docker run -e POSTGRES_PASSWORD=password postgres`
if the command appears to spin up a database, you're in good shape!
If not, follow the [Docker installation documentation](https://docs.docker.com/engine/install/)
before proceeding.
</details>
Let's add another fleck to our project:
2024-01-06 21:13:09 -06:00
```bash
npx flecks add @flecks/docker
```
2024-01-02 15:13:43 -06:00
Configure `build/flecks.yml`:
2024-01-03 16:14:01 -06:00
```yml title="build/flecks.yml"
2024-01-05 13:47:03 -06:00
'@db_test/content:./packages/content/src': {}
'@flecks/core':
id: db_test
2024-01-02 15:13:43 -06:00
'@flecks/db': {}
// highlight-start
'@flecks/db/server':
database: db
dialect: postgres
password: THIS_PASSWORD_IS_UNSAFE
username: postgres
2024-01-06 21:13:09 -06:00
// highlight-end
2024-01-02 15:13:43 -06:00
'@flecks/docker': {}
2024-01-06 21:13:09 -06:00
// highlight-start
2024-01-02 15:13:43 -06:00
'@flecks/server':
up:
- '@flecks/docker'
- '@flecks/db'
2024-01-05 13:47:03 -06:00
- '@db_test/content'
2024-01-02 15:13:43 -06:00
// highlight-end
```
Notice that we configured `@flecks/server.up` to make sure to enforce a specific order in which
our server flecks come up: first `@flecks/docker` to spin up the database, then
2024-01-05 13:47:03 -06:00
`@flecks/db` to connect to the database, and finally our `@db_test/content` fleck to interact with
2024-01-02 15:13:43 -06:00
the database. This is important!
Now `npm start` will reveal the following message in the logs:
```
@flecks/server/entry Error: Please install pg package manually
```
Pretty straightforward how to proceed:
<InstallPackage pkg="pg" />
Remember, **small core**! :smile: Now `npm start` again and you will see some new lines in the
logs:
```
@flecks/docker/container creating datadir '/tmp/flecks/flecks/docker/sequelize' +0ms
@flecks/docker/container launching: docker run --name flecks_sequelize -d --rm -p 5432:5432 -e POSTGRES_USER=postgres -e POSTGRES_DB=db -e POSTGRES_PASSWORD=THIS_PASSWORD_IS_UNSAFE -v /tmp/flecks/flecks/docker/sequelize:/var/lib/postgresql/data postgres +0ms
@flecks/docker/container 'sequelize' started +372ms
@flecks/db/server/connection config: { database: 'db', dialect: 'postgres', host: undefined, password: '*** REDACTED ***', port: undefined, username: 'postgres' } +0ms
@flecks/db/server/connection synchronizing... +2s
@flecks/db/server/connection synchronized +3ms
```
and of course, we see:
```
There were 0 tags.
There are 2 tags.
```
because we just created a new postgres database from scratch just then! Kill the application and
run `npm start` one more time and then you will see what you expect:
```
There were 2 tags.
There are 2 tags.
```
Awesome, we have a connection to a real postgres database!
<details>
<summary>Where is the docker container?</summary>
You may notice that the first time you start your application there is a delay while Docker spins
up your new database server. This is normal. You may have also noticed that subsequent runs speed
up to near-instantaneous. This is also normal!
`@flecks/docker` runs your container in a manner that outlives your application. If you kill your
application and then run:
```
docker ps
```
You will see a line for your postgres database looking something like:
```
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2024-01-06 20:31:12 -06:00
<SOME_ID> postgres "docker-entrypoint.s…" 5 minutes ago Up 5 minutes 0.0.0.0:5432->5432/tcp, :::5432->5432/tcp db_test_sequelize
2024-01-02 15:13:43 -06:00
```
2024-01-06 20:31:12 -06:00
You'll see under the `NAMES` column heading, there is an entry called `db_test_sequelize`. That's
2024-01-02 15:13:43 -06:00
our database! You can always
```
2024-01-06 20:31:12 -06:00
docker kill db_test_sequelize
2024-01-02 15:13:43 -06:00
```
to free up any resources being used. flecks keeps the containers running so that you get a nice
fast application start.
2024-01-06 20:31:12 -06:00
:::note[What's in a name?]
2024-01-02 15:13:43 -06:00
The container name is based off of `@flecks/core.id` which by default is `flecks`. If you change
2024-01-06 20:31:12 -06:00
your application's ID, the container name will be different!
2024-01-02 15:13:43 -06:00
:::
</details>
## Production
Sure, spinning up a database like magic is spiffy for development, but you probably want to be a
little less freewheeling on your production server.
Build the application we've built so far:
```
npm run build
```
Then, take a look in the `dist` directory. You'll see a file there called `docker-compose.yml`.
`@flecks/docker` automatically emits this file when you build your application for production to
make container orchestration easier. Let's take a look:
2024-01-03 16:14:01 -06:00
```yml title="dist/docker-compose.yml"
2024-01-02 15:13:43 -06:00
version: '3'
services:
flecks_app:
build:
context: ..
dockerfile: dist/Dockerfile
environment:
2024-01-04 16:11:48 -06:00
FLECKS_ENV__flecks_docker_server__enabled: 'false'
2024-01-02 15:13:43 -06:00
// highlight-next-line
2024-01-04 16:11:48 -06:00
FLECKS_ENV__flecks_db_server__host: sequelize
2024-01-02 15:13:43 -06:00
volumes:
- ../node_modules:/var/www/node_modules
// highlight-start
sequelize:
image: postgres
environment:
POSTGRES_USER: postgres
POSTGRES_DB: db
POSTGRES_PASSWORD: THIS_PASSWORD_IS_UNSAFE
// highlight-end
```
Notice our database container is included and already prepopulated with the configuration we
specified!
You can run (after you [install Docker Compose](https://docs.docker.com/compose/install/) if
necessary):
```
docker-compose -f dist/docker-compose.yml up
```
This demonstrates that your application is now being orchestrated by Docker Compose and is
chugging right along!