theme | highlighter | lineNumbers | favicon | css | align | title | layout |
---|---|---|---|---|---|---|---|
platformatic |
prism |
true |
./assets/favicon.ico |
unocss |
center |
Introducing Platformatic DB |
cover |
https://platformatic.dev/db-workshop
Platformatic DB allows you to:
- Create both OpenAPI and GraphQL schemas from your database, without having to write a single line of code.
- All customizable via Nodejs and Fastify plugins.
- Multiple databases supported: SQLite, MySQL, MariaDB, PostgreSQL
- Multiple authentication methods: JWT, WebHook, HTTP Headers
- Authorization via role based access control
Read more about Platformatic
- Node.js >= v18.8.0
- npm v8 or later
- A code editor, for example Visual Studio Code
- Basic knowledge of GraphQL
git clone https://github.com/platformatic/db-workshop.git
- Every step is incremental
- The final state of the n-th step is in
steps/step-{n}
. - We will build a full working backend for a "Movie Quotes App"
- Create a folder for the project and the backend:
mkdir -p movie-quotes/apps/movie-quotes-api/
cd
into it:
cd movie-quotes/apps/movie-quotes-api/
- init
npm
and create a platformatic project:
npx create-platformatic@latest
- Start platformatic.db:
npm start
{
"$schema": "https://platformatic.dev/schemas/v0.19.2/db",
"server": {
"hostname": "{PLT_SERVER_HOSTNAME}",
"port": "{PORT}",
"logger": {
"level": "{PLT_SERVER_LOGGER_LEVEL}"
}
},
"db": {
"connectionString": "{DATABASE_URL}",
"graphql": true,
"openapi": true
},
"migrations": {
"dir": "migrations"
},
"types": {
"autogenerate": true
}
}
Note that platformatic uses env
variables for the configuration.
-
See the reference for more information
::right::
{
"server": {
"logger": {
"level": "{PLT_SERVER_LOGGER_LEVEL}"
},
"hostname": "{PLT_SERVER_HOSTNAME}",
"port": "{PORT}"
},
"db": {
"connectionString": "{DATABASE_URL}"
},
"migrations": {
"dir": "./migrations"
}
}
PORT=3042
PLT_SERVER_HOSTNAME=127.0.0.1
PLT_SERVER_LOGGER_LEVEL=info
DATABASE_URL=sqlite://./db.sqlite
http://localhost:3042/graphiql
http://localhost:3042/documentation
- Migrate
db.sqlite
back (or you can remove thedb.sqlite
file):
npx platformatic db migrations apply --to 000
- Now we want to specify our own DB schema, edit
./migrations/001.do.sql
to be:
CREATE TABLE quotes (
id INTEGER PRIMARY KEY,
quote TEXT NOT NULL,
said_by VARCHAR(255) NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
- Remember to change
001.undo.sql
too:
DROP TABLE quotes;
- Apply the migrations:
npx platformatic db migrations apply
- You can now reload your GraphiQL and OpenAPI pages and you will automatically see the updated schemas.
- Note that migration
001.do.sql
is applied:
Platformatic is now exposing the 'quotes' entity through GraphQL and OpenAPI!
http://localhost:3042/graphiql
http://localhost:3042/documentation
- Create
./migrations/002.do.sql
:
CREATE TABLE movies (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE
);
ALTER TABLE quotes ADD COLUMN movie_id INTEGER REFERENCES movies(id);
- ...and
002.undo.sql
:
ALTER TABLE quotes DROP COLUMN movie_id;
DROP TABLE movies;
- Apply the new migration (the server will restart automatically):
npx platformatic db migrations apply
Open GraphiQL and try to create a movie:
mutation {
saveMovie(input: { name: "The Wizard of Oz" }) {
id
}
}
And (assuming movieId
is 1):
mutation {
saveQuote(
input: {
quote: "Toto, I've got a feeling we're not in Kansas anymore",
movieId: 1,
saidBy: "Dorothy Gale"}
) {
id
quote
}
}
- Query data
- The GraphQL schema can be extracted with:
npx platformatic db schema graphql >> schema.sdl
cat schema.sdl
type Query {
getQuoteById(id: ID!): Quote
quotes(limit: Int, offset: Int, orderBy: [QuoteOrderByArguments], where: QuoteWhereArguments): [Quote]
getMovieById(id: ID!): Movie
movies(limit: Int, offset: Int, orderBy: [MovieOrderByArguments], where: MovieWhereArguments): [Movie]
}
(...)
- Some code generators that can process it:
'use strict'
module.exports = async function ({ entities, db, sql }) {
await entities.graph.save({ input: { name: 'Hello' } })
await db.query(sql`
INSERT INTO graphs (name) VALUES ('Hello 2');
`)
}
-
-
db
- A database abstraction layer from @databases -
sql
- The SQL builder from @databases
-
const quotes = [
{
quote: "Toto, I've got a feeling we're not in Kansas anymore.",
saidBy: 'Dorothy Gale',
movie: 'The Wizard of Oz'
},
{
quote: "You're gonna need a bigger boat.",
saidBy: 'Martin Brody',
movie: 'Jaws'
},
{
quote: 'May the Force be with you.',
saidBy: 'Han Solo',
movie: 'Star Wars'
},
{
quote: 'I have always depended on the kindness of strangers.',
saidBy: 'Blanche DuBois',
movie: 'A Streetcar Named Desire'
}
]
module.exports = async function ({ entities, db, sql }) {
for (const values of quotes) {
const movie = await entities.movie.save({ input: { name: values.movie } })
console.log('Created movie:', movie)
const quote = {
quote: values.quote,
saidBy: values.saidBy,
movieId: movie.id
}
await entities.quote.save({ input: quote })
console.log('Created quote:', quote)
}
}
- You might want to reset the database to a clean slate by migrating to initial state (the undo scripts will drop the tables). Removing
db.sqlite
also works.
npx platformatic db migrations apply --to 000
- Then run migrations:
npx platformatic db migrations apply
- ...and seed:
npx platformatic db seed seed.js
- Create a new migration (remember the
undo
script):
ALTER TABLE quotes ADD COLUMN likes INTEGER default 0;
- Apply the migration:
npx platformatic db migrations apply
- Check
plugin.js
:
module.exports = async function (app) {}
- ...and the configuration in
platformatic.db.json
:
...
"plugins": {
"paths": [
"plugin.js"
]
}
- Platformatic DB can be extended with Fastify Plugins
- When Platformatic DB starts, loads the plugins (try adding a
app.log.info
inplugin.js
):
module.exports = async function (app) {
app.log.info('plugin loaded')
}
See the log on the server:
[10:09:20.052] INFO (146270): running 003.do.sql
[10:09:20.129] INFO (146270): plugin loaded
[10:09:20.209] INFO (146270): server listening
url: "http://127.0.0.1:3042"
const S = require('fluent-json-schema')
module.exports = async function plugin (app) {
app.log.info('plugin loaded')
const schema = {
params: S.object().prop('id', app.getSchema('Quote').properties.id)
}
app.post('/quotes/:id/like', { schema }, async function (request, response) {
const { db, sql } = app.platformatic
const result = await db.query(sql`
UPDATE quotes SET likes = likes + 1 WHERE id=${request.params.id} RETURNING likes
`)
return result[0]?.likes
})
}
- We can extract a
incrementQuoteLikes
function for reuse inplugin.js
:
const S = require('fluent-json-schema')
module.exports = async function plugin (app) {
app.log.info('plugin loaded')
async function incrementQuoteLikes (id) {
const { db, sql } = app.platformatic
const result = await db.query(sql`
UPDATE quotes SET likes = likes + 1 WHERE id=${id} RETURNING likes
`)
return result[0]?.likes
}
const schema = {
params: S.object().prop('id', app.getSchema('Quote').properties.id)
}
app.post('/quotes/:id/like', { schema }, async function (request, response) {
return { likes: await incrementQuoteLikes(request.params.id) }
})
}
module.exports = async function plugin (app) {
// ...
app.graphql.extendSchema(`
extend type Mutation {
likeQuote(id: ID!): Int
}
`)
app.graphql.defineResolvers({
Mutation: {
likeQuote: async (_, { id }) => await incrementQuoteLikes(id)
}
})
}
-
Remember you cloned the workshop? Grab
./steps/07-ui/movie-quotes/apps/movie-quotes-frontend
and copy the foldermovie-quotes-frontend
asidemovie-quotes-api
-
In the frontend folder, run:
npm install
npm start
You should see this:
➜ npm start
> start
> astro dev --port 3000
🚀 astro v1.9.2 started in 72ms
┃ Local http://localhost:3000/
┃ Network use --host to expose
- On server setup CORS in
platformatic.db.json
using the same local URL:
{
"server": {
"hostname": "127.0.0.1",
"port": 3042
"cors": {
"origin": "http://localhost:3000"
}
},
...
}
Restart the Platformatic DB server(not needed! 🎉)
- Point your browser to https://platformatic.cloud
- Login with github (you may beed to accept the terms and conditions if not done yet)
- You should see the Dashboard page on your personal organization, which is created automatically. Click on "Create an App"
- You can also download the
env
file with both workspaceId and the key. - The file is called
${workspace-name}.plt.txt
- Save it in
movie-quotes/apps/movie-quotes-api
folder in your project
- Go in the
movie-quotes-api
folder:
cd ./movie-quotes/apps/movie-quotes-api
- Launch the deployment:
npx platformatic deploy --keys=./test-workspace-demo.plt.txt
curl https://gorgeous-vacuous-young-chalk.deploy.space/quotes
- Open
movie-quotes/apps/movie-quotes-frontend/.env
and changePUBLIC_GRAPHQL_API_ENDPOINT
to the deployed API endpoint (use the actual URL):
PUBLIC_GRAPHQL_API_ENDPOINT=https://gorgeous-vacuous-young-chalk.deploy.space/graphql
- Restart the frontend dev server and check the UI is still working
- Add security integrating with a third party authentication service (like Auth0, see how here).
- Add authorizations at API level (see references)
- Generate TypeScript types