Baze de date
Construiești un backend care are nevoie să stocheze ceva? Fiecare aplicație reală o face până la urmă - todos, utilizatori, scoruri. Baze de date îți oferă o mică bază de date administrată PostgreSQL găzduită pe DojoCode: o creezi cu un singur clic, o explorezi și o interoghezi într-un editor de date integrat și te conectezi la ea din proiectele tale exact așa cum ai face-o în producție - printr-o variabilă de mediu.
Postgres real, zero configurare
Fiecare bază de date este o bază de date PostgreSQL reală, cu propriile credențiale generate și un rol izolat - niciun alt utilizator nu se poate conecta la ea. Nu trebuie să instalezi nimic și te poți conecta și de pe propria mașină folosind orice client Postgres.
Crearea unei baze de date
Mergi la My Databases și dă clic pe New database. Alege un nume de afișare (numele real al bazei de date Postgres și credențialele sunt generate pentru tine) - crearea este instantanee.


Fiecare bază de date își afișează statusul, utilizarea spațiului de stocare raportată la cotă și acțiunile pentru deschiderea editorului de date sau ștergerea acesteia.
Ștergerea este permanentă
Ștergerea unei baze de date o elimină din cluster împreună cu toate datele sale, iar orice proiect care o folosește va pierde conexiunea. Această acțiune nu poate fi anulată.
Editorul de date
Deschide o bază de date pentru a ajunge în editorul său de date - un spațiu de lucru pe toată lățimea ecranului, cu două taburi:
- Query - un editor SQL (cu evidențiere de sintaxă și Ctrl/⌘ + Enter pentru rulare) care se execută pe baza ta de date. Sunt acceptate scripturi cu instrucțiuni multiple; rezultatele, numărul de rânduri și timpii de execuție sunt afișați mai jos.
- Tables - o vizualizare în timp real a schemei tale: fiecare tabel cu coloanele și tipurile sale, plus o previzualizare la un singur clic (Preview) care rulează o interogare
SELECT * … LIMIT 20pentru tine.

Dacă dai clic pe un tabel în tabul Tables, o interogare SELECT gata pregătită este introdusă în editor, astfel încât navigarea prin datele existente nu necesită scriere:

Interogările se execută sub rolul izolat al propriei baze de date, având o limită de timp pentru instrucțiuni (statement timeout) și o limită pentru numărul de rezultate - interogările care rulează prea mult sau scapă de sub control sunt oprite automat.
Un script cu instrucțiuni multiple rulează ca o singură tranzacție: dacă vreo instrucțiune eșuează, întregul script este anulat (rolled back) și nimic nu rămâne aplicat pe jumătate. Instrucțiunile pe care Postgres refuză să le execute în interiorul unei tranzacții (VACUUM, CREATE INDEX CONCURRENTLY) funcționează în continuare atunci când le rulezi separat.
Detalii de conectare
Panoul Connection arată tot ce are nevoie un client Postgres: gazdă, port, bază de date, utilizator, parolă și un URL postgres:// gata de lipit - fiecare cu copiere printr-un singur clic. Parola și URL-ul sunt mascate până când le dezvălui și sunt preluate doar atunci când le soliciți.

- Doar tu poți vedea parola. Există într-un singur loc - acest panou, în propria ta bază de date. Nu este afișată niciodată altor utilizatori sau asistentului AI.
- Schimbă parola oricând. Rotate password generează o parolă nouă și deconectează fiecare client care o folosește pe cea veche. Proiectele la care este atașată baza de date vor prelua automat noua parolă la următoarea lor rulare, iar orice site publicat care o folosește este actualizat pe loc - nu este necesară republicarea.
- Conectează-te de oriunde. Șirul de conexiune funcționează și de pe propriul laptop -
psql, TablePlus, DBeaver sau orice driver Postgres.
Utilizarea unei baze de date într-un proiect
Șabloanele de proiect server-side au un tab Database lângă cel de Secrets. Atașează una dintre bazele tale de date acolo și mediul de execuție va injecta conexiunea ca variabile de mediu:
| Variabilă | Valoare |
|---|---|
DATABASE_URL | Șirul de conexiune complet postgres://user:password@host:port/db |
DATABASE_HOST / DATABASE_PORT | Gazdă și port |
DATABASE_NAME / DATABASE_USER / DATABASE_PASSWORD | Părți individuale |

Variabilele sunt injectate prin aceeași conductă ca și secretele, așa că mecanismul de citire este cel pe care șablonul tău îl folosește deja pentru secrete - process.env.NAME în șabloanele Node, os.environ["NAME"] în Python și așa mai departe (vezi tabelul complet per șablon). Un secret explicit numit DATABASE_URL suprascrie valoarea bazei de date atașate.
Doar șabloane server-side
Tabul Database apare în NodeJS, NodeTS, NestJS, Fastify, Hono, Python, PHP, Java, C, C++, Ruby, Rust, C# și Go. Șabloanele de frontend, full-stack, mobile și SQL-playground rulează în întregime în previzualizarea din browser, care nu are socket TCP pentru a accesa Postgres - deci nu au tabul Database. Pentru a pune o bază de date în spatele unei aplicații de browser, construiește API-ul în unul dintre șabloanele de server de mai sus și apelează-l din frontend.
Șabloane JavaScript de backend
NodeJS, NodeTS, Fastify, Hono și NestJS rulează prin intermediul butonului Run, deci se conectează cu adevărat. Adaugă mai întâi pachetul pg în panoul Dependencies. Toate exemplele presupun că baza de date atașată are tabelul todos din secțiunea dedicată editorului de date.
Previzualizarea din editor și API tester ajung de asemenea la ea
Previzualizarea din browser nu are socketuri TCP, așa că DojoCode răspunde la require("pg") acolo cu un înlocuitor integrat: codul tău funcționează neschimbat și fiecare interogare este retransmisă prin DojoCode către propria bază de date atașată. Acesta acoperă API-ul de zi cu zi (Pool, Client, query cu substituenți $n, callback-uri sau promisiuni); fiecare instrucțiune rulează în propria tranzacție, așa că un flux cu instrucțiuni multiple BEGIN/COMMIT necesită utilizarea butonului Run sau a aplicației publicate, care folosesc driverul real.
const { Client } = require("pg");
async function main() {
if (!process.env.DATABASE_URL) {
console.log("No database attached - attach one in the Database panel.");
return;
}
const client = new Client({ connectionString: process.env.DATABASE_URL });
await client.connect();
const { rows } = await client.query("SELECT * FROM todos ORDER BY id");
console.log(rows);
await client.end();
}
main().catch((error) => console.error("Database error:", error.message));const Fastify = require("fastify");
const { Pool } = require("pg");
const app = Fastify();
const pool = process.env.DATABASE_URL
? new Pool({ connectionString: process.env.DATABASE_URL })
: null;
app.get("/todos", async () => {
if (!pool) return { error: "No database attached." };
const { rows } = await pool.query("SELECT * FROM todos ORDER BY id");
return rows;
});
app.listen({ port: 3000, host: "0.0.0.0" });const { Hono } = require("hono");
const { Pool } = require("pg");
const app = new Hono();
const pool = process.env.DATABASE_URL
? new Pool({ connectionString: process.env.DATABASE_URL })
: null;
app.get("/todos", async (context) => {
if (!pool) return context.json({ error: "No database attached." });
const { rows } = await pool.query("SELECT * FROM todos ORDER BY id");
return context.json(rows);
});
module.exports = app;// todos.service.ts
import { Injectable } from "@nestjs/common";
import { Pool } from "pg";
@Injectable()
export class TodosService {
private readonly pool = process.env.DATABASE_URL
? new Pool({ connectionString: process.env.DATABASE_URL })
: null;
async findAll() {
if (!this.pool) return { error: "No database attached." };
const { rows } = await this.pool.query("SELECT * FROM todos ORDER BY id");
return rows;
}
}Python
Rulează și prin intermediul butonului Run. Adaugă psycopg2-binary în panoul Dependencies:
import os
import psycopg2
url = os.environ.get("DATABASE_URL")
if not url:
print("No database attached - attach one in the Database panel.")
else:
connection = psycopg2.connect(url)
with connection.cursor() as cursor:
cursor.execute("SELECT * FROM todos ORDER BY id")
for row in cursor.fetchall():
print(row)
connection.close()Și previzualizarea din browser o poate accesa
Proiectele Python pot, de asemenea, să activeze web preview și API tester, care rulează codul tău în browser prin intermediul WebAssembly. Acel runtime nu are un socket TCP, așa că DojoCode oferă un înlocuitor integrat pentru psycopg2 acolo: codul tău import psycopg2 funcționează neschimbat, iar fiecare interogare este retransmisă prin DojoCode către propria bază de date atașată. Terminalul previzualizării ar putea menționa că psycopg2-binary nu are un build pentru browser - acest lucru este de așteptat; dependența este pentru Run și aplicația publicată, iar previzualizarea folosește înlocuitorul de fiecare dată când o bază de date este atașată.
Retransmiterea acoperă tiparele uzuale de interogare (connect, cursor, execute cu substituenți %s, fetches, RealDictCursor); fiecare instrucțiune rulează ca o tranzacție proprie, așa că commit și rollback sunt acceptate ca operațiuni fără efect (no-ops). Butonul Run și proiectul publicat se conectează direct folosind driverul real în schimb.
Limbaje compilate și alte limbaje
Ruby, Go, Rust, PHP, Java, C#, C și C++ rulează, de asemenea, prin intermediul butonului Run, astfel încât variabilele de conexiune sunt prezente în mediul de rulare. Fiecare are nevoie mai întâi ca driverul său Postgres să fie adăugat în panoul Dependencies - numele driverului este menționat deasupra fiecărui exemplu de mai jos.
Majoritatea driverelor folosesc direct DATABASE_URL. JDBC pentru Java și Npgsql pentru C# folosesc în schimb propriile formate de șiruri de conexiune, așa că acestea două construiesc șirul din variabilele separate DATABASE_HOST / DATABASE_PORT / DATABASE_NAME / DATABASE_USER / DATABASE_PASSWORD.
require 'pg'
url = ENV['DATABASE_URL'].to_s
if url.empty?
puts 'No database attached - attach one in the Database panel.'
else
conn = PG.connect(url)
conn.exec('SELECT id, title FROM todos ORDER BY id') do |rows|
rows.each { |row| puts "##{row['id']} - #{row['title']}" }
end
conn.close
endpackage main
import (
"database/sql"
"fmt"
"os"
_ "github.com/lib/pq"
)
func main() {
url := os.Getenv("DATABASE_URL")
if url == "" {
fmt.Println("No database attached - attach one in the Database panel.")
return
}
db, err := sql.Open("postgres", url)
if err != nil {
fmt.Println("open error:", err)
return
}
defer db.Close()
rows, err := db.Query("SELECT id, title FROM todos ORDER BY id")
if err != nil {
fmt.Println("query error:", err)
return
}
defer rows.Close()
for rows.Next() {
var id int
var title string
rows.Scan(&id, &title)
fmt.Printf("#%d - %s\n", id, title)
}
}use native_tls::TlsConnector;
use postgres::Client;
use postgres_native_tls::MakeTlsConnector;
use std::env;
fn main() {
let url = env::var("DATABASE_URL").unwrap_or_default();
if url.is_empty() {
println!("No database attached - attach one in the Database panel.");
return;
}
// The database accepts only encrypted connections, so `NoTls` cannot connect.
let connector = MakeTlsConnector::new(TlsConnector::new().expect("tls setup failed"));
let mut client = Client::connect(&url, connector).expect("connection failed");
for row in client
.query("SELECT id, title FROM todos ORDER BY id", &[])
.expect("query failed")
{
let id: i32 = row.get(0);
let title: String = row.get(1);
println!("#{} - {}", id, title);
}
}<?php
$url = getenv('DATABASE_URL');
if ($url === false || $url === '') {
echo "No database attached - attach one in the Database panel.\n";
return;
}
$conn = pg_connect($url);
$result = pg_query($conn, 'SELECT id, title FROM todos ORDER BY id');
while ($row = pg_fetch_assoc($result)) {
echo '#' . $row['id'] . ' - ' . $row['title'] . "\n";
}
pg_close($conn);import java.sql.*;
public class HelloWorld {
public static void main(String[] args) throws Exception {
String host = System.getenv("DATABASE_HOST");
if (host == null || host.isEmpty()) {
System.out.println("No database attached - attach one in the Database panel.");
return;
}
// JDBC has its own URL format - build it from the separate variables.
String jdbcUrl = "jdbc:postgresql://" + host + ":" + System.getenv("DATABASE_PORT")
+ "/" + System.getenv("DATABASE_NAME");
try (Connection conn = DriverManager.getConnection(
jdbcUrl, System.getenv("DATABASE_USER"), System.getenv("DATABASE_PASSWORD"));
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery("SELECT id, title FROM todos ORDER BY id")) {
while (rs.next()) {
System.out.println("#" + rs.getInt("id") + " - " + rs.getString("title"));
}
}
}
}using System;
using Npgsql;
var host = Environment.GetEnvironmentVariable("DATABASE_HOST");
if (string.IsNullOrEmpty(host))
{
Console.WriteLine("No database attached - attach one in the Database panel.");
return;
}
// Npgsql uses key/value connection strings, not the DATABASE_URL format.
var connString =
$"Host={host};Port={Environment.GetEnvironmentVariable("DATABASE_PORT")};" +
$"Database={Environment.GetEnvironmentVariable("DATABASE_NAME")};" +
$"Username={Environment.GetEnvironmentVariable("DATABASE_USER")};" +
$"Password={Environment.GetEnvironmentVariable("DATABASE_PASSWORD")}";
using var conn = new NpgsqlConnection(connString);
conn.Open();
using var cmd = new NpgsqlCommand("SELECT id, title FROM todos ORDER BY id", conn);
using var reader = cmd.ExecuteReader();
while (reader.Read())
{
Console.WriteLine($"#{reader.GetInt32(0)} - {reader.GetString(1)}");
}#include <stdio.h>
#include <stdlib.h>
#include <libpq-fe.h>
int main() {
const char *url = getenv("DATABASE_URL");
if (url == NULL || url[0] == '\0') {
printf("No database attached - attach one in the Database panel.\n");
return 0;
}
PGconn *conn = PQconnectdb(url);
if (PQstatus(conn) != CONNECTION_OK) {
printf("connection error: %s\n", PQerrorMessage(conn));
PQfinish(conn);
return 1;
}
PGresult *res = PQexec(conn, "SELECT id, title FROM todos ORDER BY id");
if (PQresultStatus(res) != PGRES_TUPLES_OK) {
printf("query error: %s\n", PQerrorMessage(conn));
} else {
for (int i = 0; i < PQntuples(res); i++) {
printf("#%s - %s\n", PQgetvalue(res, i, 0), PQgetvalue(res, i, 1));
}
}
PQclear(res);
PQfinish(conn);
return 0;
}#include <cstdlib>
#include <iostream>
#include <string>
#include <libpq-fe.h>
int main() {
const char *url = std::getenv("DATABASE_URL");
if (url == nullptr || url[0] == '\0') {
std::cout << "No database attached - attach one in the Database panel.\n";
return 0;
}
PGconn *conn = PQconnectdb(url);
if (PQstatus(conn) != CONNECTION_OK) {
std::cout << "connection error: " << PQerrorMessage(conn) << "\n";
PQfinish(conn);
return 1;
}
PGresult *res = PQexec(conn, "SELECT id, title FROM todos ORDER BY id");
if (PQresultStatus(res) != PGRES_TUPLES_OK) {
std::cout << "query error: " << PQerrorMessage(conn) << "\n";
} else {
for (int i = 0; i < PQntuples(res); i++) {
std::cout << "#" << PQgetvalue(res, i, 0)
<< " - " << PQgetvalue(res, i, 1) << "\n";
}
}
PQclear(res);
PQfinish(conn);
return 0;
}Limbajele C și C++ partajează același pachet libpq și același API C - diferența constă doar în modul în care scrii: printf și getenv în C, std::cout și std::getenv în C++.
Fiecare vine cu propria bază de date
Atașarea este per utilizator, per proiect - exact ca secretele. Atunci când altcineva deschide (sau face fork la) proiectul tău, nu primește baza ta de date: atașează una proprie în aceeași filă Database. Datele și acreditările tale nu sunt niciodată partajate.
Bazele de date și publicarea
O bază de date atașată nu te împiedică să publici - valorile DATABASE_* sunt variabile de mediu la runtime, nu sunt niciodată incluse în bundle-ul publicat. Publicarea este blocată doar atunci când proiectul folosește secrete.
Publică o aplicație de server (NestJS, Fastify, Hono sau un proiect Python cu testerul de API pornit) cu o bază de date atașată, iar containerul acesteia va porni cu aceleași variabile DATABASE_* pe care ți le oferă editorul - site-ul live comunică cu o bază de date reală, așa că codul pe care l-ai testat cu Run este codul care este livrat.
În editor, backend-urile Python și Node interoghează baza de date prin releul DojoCode (vezi secțiunile lor de mai sus); containerul publicat este locul în care codul tău comunică pentru prima dată cu Postgres direct, printr-un socket real cu driverul real.
Două lucruri de știut despre aplicația publicată:
- Rulează pe baza ta de date. Un site publicat este o singură instanță partajată, așa că solicitarea fiecărui vizitator ajunge la baza de date atașată a proprietarului - spre deosebire de editor, unde fiecare persoană rulează pe propria instanță. Nu publica o aplicație care le permite vizitatorilor să scrie date pe care ai prefera să le păstrezi curate.
- Modificările ajung la ea fără republicare. Atașarea, detașarea sau rotirea parolei actualizează și site-ul live - următoarea solicitare pornește un container nou care conține noile valori, așa că acordă un moment pentru acea primă solicitare în timp ce pornește.
Bazele de date și Alex (asistentul AI)
Alex știe dacă proiectul are o bază de date atașată și cum se numește - niciodată parola. Poate lista bazele tale de date, le poate inspecta tabelele și coloanele și poate scrie cod care citește corect DATABASE_URL pentru șablonul tău, previne erorile în cazul unei atașări lipsă și se potrivește cu schema ta reală în loc să ghicească. Detaliile de conexiune pe care le preia sunt întotdeauna cenzurate; acreditările ajung la codul tău doar prin intermediul mediului de rulare.
Limite și lucruri utile de știut
- Până la 3 baze de date per cont, cu o cotă de stocare de 50 MB fiecare (soft - după o limită maximă, sunt acceptate doar interogări de citire până când eliberezi spațiu).
- O bază de date complet nouă nu are 0 octeți. Postgres livrează fiecare bază de date cu propriile cataloage de sistem, așa că una goală raportează deja în jur de 7 MB din cotă înainte de a crea un singur tabel - aceasta este baza, nu o scurgere de memorie (leak), și îți lasă restul pentru a lucra.
- Numele bazelor de date sunt doar nume de afișare; identificatorii și acreditările reale Postgres sunt generate și nu conțin niciodată date introduse de utilizator.
- Interogările din editor au un timp de expirare (statement timeout) și o limită de conexiuni per rol - o interogare blocată nu îți va bloca baza de date.
- Conexiunile sunt criptate - șirul de conexiune conține
?sslmode=require, iar conexiunile în text simplu sunt refuzate. Păstrează acest parametru dacă construiești singur URL-ul. - Bazele de date intră în repaus atunci când sunt inactive și se activează la următoarea conexiune, așa că prima interogare după o perioadă de inactivitate poate dura un moment în plus. Într-o aplicație publicată, preferă un pool de conexiuni cu un timp scurt de expirare pentru inactivitate în locul unui singur client pe care îl ții deschis la nesfârșit.
- Noile valori
DATABASE_*sunt preluate la următoarea rulare după atașare, detașare sau rotire - nu este necesară o reîncărcare manuală. - Bazele de date sunt disponibile doar pe șabloanele de pe partea de server. Locurile de joacă (playgrounds) pentru frontend, full-stack (Next.js, Astro, SvelteKit, Remix), mobile și PGlite / SQLite nu au o filă Database: codul lor rulează în previzualizarea din browser, care nu poate deschide o conexiune Postgres.