The interaction with the database normally starts with a call to connect()
,
which establishes a new database session. This results in getting a
connection Promise
. The connection instance provides methods to
run queries.
const edgedb = require("edgedb");
async function main() {
// Establish a connection to an existing database
// named "test" as an "edgedb" user.
const conn = await edgedb.connect("edgedb://edgedb@localhost/test");
try {
// Create a User object type.
await conn.execute(`
CREATE TYPE User {
CREATE REQUIRED PROPERTY name -> str;
CREATE PROPERTY dob -> cal::local_date;
}
`);
// Insert a new User object.
await conn.query(
`
INSERT User {
name := <str>$name,
dob := <cal::local_date>$dob
}
`,
{
name: "Bob",
dob: new edgedb.LocalDate(1984, 3, 1)
}
);
// Select User objects.
let userSet = await conn.query(
"SELECT User {name, dob} FILTER .name = <str>$name",
{ name: "Bob" }
);
// *userSet* now contains
// Set{Object{name := 'Bob',
// dob := datetime.date(1984, 3, 1)}}
console.log(userSet);
} finally {
conn.close()
}
}
main();
edgedb
automatically converts EdgeDB types to the corresponding
JavaScript types and vice versa. See Datatypes for details.
For server-side applications that handle frequent requests and need database connections for short periods of time, it is recommended to use a connection pool. The edgedb-js API provides an implementation of such pool.
To create a connection pool, use the createPool()
method.
The resulting edgedb.Pool
object can be used to maintain
a certain number of open connections and borrow them when needed.
const edgedb = require("edgedb");
async function main() {
// Create a connection pool to an existing database
const pool = await edgedb.createPool("edgedb://edgedb@localhost/test");
try {
// Create a User object type.
await pool.execute(`
CREATE TYPE User {
CREATE REQUIRED PROPERTY name -> str;
CREATE PROPERTY dob -> cal::local_date;
}
`);
// Insert a new User object.
await pool.query(
`
INSERT User {
name := <str>$name,
dob := <cal::local_date>$dob
}
`,
{
name: "Bob",
dob: new edgedb.LocalDate(1984, 3, 1)
}
);
// Select User objects.
let userSet = await pool.query(
"SELECT User {name, dob} FILTER .name = <str>$name",
{ name: "Bob" }
);
// *userSet* now contains
// Set{Object{name := 'Bob',
// dob := datetime.date(1984, 3, 1)}}
console.log(userSet);
} finally {
await pool.close();
}
}
main();
See Pool API documentation for more information.
The most robust way to execute transactional code is to use
the retryingTransaction()
API:
await pool.retryingTransaction(tx => {
await tx.execute("INSERT User {name := 'Don'}");
});
Note that we execute queries on the tx
object in the above
example, rather than on the original connection pool pool
object.
The retryingTransaction()
API guarantees that:
Transactions are executed atomically;
If a transaction is failed for any of the number of transient errors (i.e. a network failure or a concurrent update error), the transaction would be retried;
If any other, non-retryable exception occurs, the transaction is rolled back,
and the exception is propagated, immediately aborting the
retryingTransaction()
block.
The key implication of retrying transactions is that the entire nested code block can be re-run, including any non-querying JavaScript code. Here is an example:
pool.retryingTransaction(tx => {
let user = await tx.queryOne(
"SELECT User { email } FILTER .login = <str>$login",
login=login,
)
let query = await fetch(
'https://service.local/email_info', {
body: JSON.stringify({email: user.email})
headers: { 'Content-Type': 'application/json' },
},
)
let data = await query.json()
user = await tx.queryOne('''
UPDATE User FILTER .login = <str>$login
SET { email_info := <json>$data}
''',
login=login,
data=data,
)
})
In the above example, the execution of the HTTP request would be retried too. The core of the issue is that whenever transaction is interrupted user might have the email changed (as the result of concurrent transaction), so we have to redo all the work done.
Generally it’s recommended to not execute any long running code within the transaction unless absolutely necessary.
Transactions allocate expensive server resources and having too many concurrently running long-running transactions will negatively impact the performance of the DB server.
See also: