Skip to main content
Version: 3.0.6

Fullstack Guide

Welcome to your next steps with Platformatic applications such as Platformatic Watt, Platformatic DB with SQLite, and the Platformatic Gateway.

In this tutorial, you will build a movie quotes application, where users can add, like and delete quotes from popular movies. This guide will help you setup and run your first full-stack Platformatic application.

note

While this guide uses SQLite, Platformatic DB also supports PostgreSQL, MySQL, and MariaDB. For more details on database compatibility, see the Platformatic DB documentation.

Prerequisites

Before starting, ensure you have the following installed:

Create a Watt Project with a Platformatic DB application

To start the Watt creator wizard, run the appropriate command for your package manager in your terminal:

npm create wattpm

This interactive command-line tool will guide you through setting up a new Watt project. For this guide, please choose the following options:

Hello YOURNAME, welcome to Watt 3.0.0!
? Where would you like to create your project? .
✔ Installing @platformatic/runtime@^3.0.0 using npm ...
? Which kind of application do you want to create? @platformatic/db
✔ Installing @platformatic/db@^3.0.0 using npm ...
? What is the name of the application? db
? What is the connection string? sqlite://./db.sqlite
? Do you want to create default migrations? yes
? Do you want to use TypeScript? no
? Do you want to create another application? no
? What port do you want to use? 3042

After completing the wizard, your Watt application will be ready in the specified folder. This includes example migration files, plugin scripts, routes, and tests within your application directory.

note

If the wizard does not handle dependency installation, ensure to run npm/yarn/pnpm install command manually.

wattpm sets up workspaces for the selected package manager. Running a manual installation with a different package manager may cause issues or trigger warnings.

Start Your API Server

Run the following command in your project directory to start your API server:

npm run dev

Your API server is now live! 🌟 It will automatically serve REST and GraphQL interfaces for your SQL database.

Create a Database Schema

Navigate to the migrations directory within the web/db folder of your project directory. This folder contains your database migration files:

  • 001.do.sql: contains the SQL statements for creating database objects.
  • 001.undo.sql: contains the SQL statements to remove database objects.

For the quote application, you will need a schema configuration for the quotes table and likes. Add the schema configuration below in the 001.do.sql file to do this:

CREATE TABLE quotes (
id INTEGER PRIMARY KEY,
quote TEXT NOT NULL,
said_by VARCHAR(255) NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

This SQL query creates a database table called "quotes" that stores: a unique ID number, the quote's text, who said it, and when it was added to the database.

Create a new file 002.do.sql in the same folder directory and add a schema below:

ALTER TABLE quotes ADD COLUMN likes INTEGER default 0;

This schema adds the likes column.

Finally, execute your migrations by running:

wattpm db:migrations:apply

Create a Like Quotes Plugin

You will add a Fastify plugin for adding likes to a quote. Create a new file like-quote.js in the plugins directory of your DB applications.

export default async function plugin (app) {
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 *
`)
return result
}

const schema = {
params: {
type: 'object',
properties: {
id: app.getSchema('Quote').properties.id
},
required: ['id'],
additionalProperties: false
}
}

// Check if the route already exists
if (!app.hasRoute({ method: 'POST', url: '/quotes/:id/like' })) {
app.post('/quotes/:id/like', { schema }, async function (request) {
return { likes: await incrementQuoteLikes(request.params.id) }
})
}
}

Here, you've created a API endpoint that lets users like a quote in the database. incrementQuoteLikes function updates the database by adding 1 to the quote's likes count. The schema checks that the quote ID is valid.

Apply Schema Migrations

Run the command below for database migrations

npx wattpm db:migrations:apply

Add a Gateway application

Platformatic Gateway integrates different microservices into a single API for more efficient management. For the movie quotes application, you will use the Platformatic gateway to aggregate the DB application, and your frontend application.

Let's create a new Platformatic Gateway

npm create wattpm

This will output:

Hello YOURNAME, welcome to Watt 3.0.0!
Using existing configuration ...
? Which kind of application do you want to create? @platformatic/gateway
? What is the name of the application? gateway
? Do you want to use TypeScript? no
? Do you want to create another application? no
? Which application should be exposed? gateway

Add applications to gateway

In your web/gateway directory, select the platformatic.json file and add the DB application to your gateway:

{
"$schema": "https://schemas.platformatic.dev/@platformatic/gateway/3.0.0.json",
"gateway": {
"applications": [
{
"id": "db",
"openapi": {
"url": "/documentation/json"
}
}
],
"refreshTimeout": 1000
},
"watch": true
}

Add a React frontend for Movie Quotes App

Next steps is to add a React (vite) frontend for the movie quotes app. Run the command to create a React.js application:

npm create vite@latest -- web/frontend --template react

Now run wattpm-utils to import the frontend into Watt. The command will output:

[15:19:48.110] INFO (54409): Application frontend is using Vite. Adding @platformatic/vite to its package.json dependencies.
[15:19:48.132] INFO (54409): Installing dependencies for the project using npm ...
[15:19:56.608] INFO (54409): Installing dependencies for the application db using npm ...
[15:20:00.643] INFO (54409): Installing dependencies for the application frontend using npm ...
[15:20:01.257] INFO (54409): Installing dependencies for the application gateway using npm ...

Setting Up the Frontend Client

To kickstart the project, in your web/frontend/src directory, run the command to create a Massimo client for your remote server:

npx massimo-cli --frontend http://0.0.0.0:3042 --name client -f web/frontend/src/client

This command will generate a Platformatic frontend client in the specified web/frontend/src folder, which allows a more efficient communication between your frontend and Platformatic DB and gateway application.

Installed Required Packages

To style the application, install the following CSS packages:

npm install tailwindcss postcss autoprefixer

Set up Tailwind CSS by creating the necessary configuration files:

npm create tailwindcss

Ensure your tailwind.config.js points to the correct paths for your components:

export default {
content: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
theme: {
extend: {}
},
plugins: []
}

Building the QuoteList Component

The core of your frontend lies in the src/components/QuoteList.jsx file. Here's how it works:

  • Data Management:
    • Fetches quotes from the API.
    • Handles creating new quotes, liking existing ones, and deleting quotes.
  • State Handling:
    • Manages loading, error states, and real-time updates for user interactions.

Here’s the complete QuoteList component:

import { useState, useEffect } from 'react'
import { setBaseUrl, dbGetQuotes, dbCreateQuote, dbDeleteQuotes, postQuotesIdLike } from '../client/client.mjs'

// Set the base URL for the API client
setBaseUrl(window.location.origin) // Or your specific API base URL

export default function QuoteList () {
const [quotes, setQuotes] = useState([])
const [error, setError] = useState(null)
const [newQuote, setNewQuote] = useState({ quote: '', saidBy: '' })
const [isLoading, setIsLoading] = useState(true)

useEffect(() => {
getQuotes()
}, [])

const getQuotes = async () => {
try {
setIsLoading(true)
const fetchedQuotes = await dbGetQuotes({})
setQuotes(fetchedQuotes)
setError(null)
} catch (error) {
console.error('Error fetching quotes:', error)
setError('Failed to fetch quotes')
} finally {
setIsLoading(false)
}
}

const handleLike = async id => {
try {
await postQuotesIdLike({ id })
getQuotes()
} catch (error) {
console.error('Error liking quote:', error)
setError('Failed to like quote')
}
}

const handleCreate = async e => {
e.preventDefault()
if (!newQuote.quote || !newQuote.saidBy) {
setError('Please fill in both quote and author')
return
}
try {
await dbCreateQuote(newQuote)
setNewQuote({ quote: '', saidBy: '' })
setError(null)
getQuotes()
} catch (error) {
console.error('Error creating quote:', error)
setError('Failed to create quote')
}
}

const handleDelete = async id => {
try {
await dbDeleteQuotes({ id })
getQuotes()
} catch (error) {
console.error('Error deleting quote:', error)
setError('Failed to delete quote')
}
}

return (
<div className="min-h-screen flex flex-col items-center justify-center bg-gray-100 py-8">
<div className="bg-white shadow-md rounded-lg p-8 max-w-lg w-full">
<h1 className="text-3xl font-bold mb-8 text-center text-gray-800">Movie Quotes</h1>

{error && <div className="mb-4 p-4 bg-red-100 border border-red-400 text-red-700 rounded-lg">{error}</div>}

<form onSubmit={handleCreate} className="mb-8">
<div className="flex flex-col sm:flex-row gap-4 mb-4">
<input
type="text"
placeholder="Enter Quote"
value={newQuote.quote}
onChange={e => setNewQuote({ ...newQuote, quote: e.target.value })}
className="w-full p-3 border border-gray-300 rounded-lg shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
<input
type="text"
placeholder="Said By"
value={newQuote.saidBy}
onChange={e => setNewQuote({ ...newQuote, saidBy: e.target.value })}
className="w-full p-3 border border-gray-300 rounded-lg shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
<button
type="submit"
className="w-full bg-blue-500 text-white py-3 rounded-lg hover:bg-blue-600 transition duration-200 shadow-md"
>
Add Quote
</button>
</form>

{isLoading ? (
<p className="text-center text-gray-500 text-lg">Loading quotes...</p>
) : quotes.length === 0 ? (
<p className="text-center text-gray-500 text-lg">No quotes available</p>
) : (
<div className="space-y-4">
{quotes.map(quote => (
<div key={quote.id} className="bg-gray-50 p-4 rounded-lg shadow-sm border border-gray-200">
<p className="text-xl font-semibold text-gray-800 mb-1">"{quote.quote}"</p>
<p className="text-md text-gray-600 mb-3">- {quote.saidBy}</p>
<div className="flex justify-between items-center">
<button
onClick={() => handleLike(quote.id)}
className="flex items-center space-x-2 text-blue-500 hover:text-blue-600"
>
<span className="text-2xl">❤️</span>
<span className="text-xl">{quote.likes || 0}</span>
</button>
<button
onClick={() => handleDelete(quote.id)}
className="text-red-500 hover:text-red-600 px-3 py-1 border border-red-500 rounded-lg hover:bg-red-50 transition duration-200"
>
Delete
</button>
</div>
</div>
))}
</div>
)}
</div>
</div>
)
}

Integrating the QuoteList Component

Update your main app file src/App.jsx to include the QuoteList component:

import QuoteList from './components/QuoteList'

export default function App () {
return (
<div className="App">
<QuoteList />
</div>
)
}

Add frontend to Gateway

In your web/gateway directory, add the frontend id to your gateway platformatic.json file, update it as shown below:

{
"$schema": "https://schemas.platformatic.dev/@platformatic/gateway/3.0.0.json",
"gateway": {
"applications": [
{
"id": "db",
"openapi": {
"url": "/documentation/json"
}
},
{
"id": "frontend"
}
],
"refreshTimeout": 1000
},
"watch": true
}

Start Your API Server

In your project directory, use the Platformatic CLI to start your API server:

npm run dev

This will:

  • Automatically map your SQL database and React frontend to REST using the gateway
  • Start the Platformatic Watt server.

Your Platformatic application is now up and running! 🌟

Movie quotes application