Basic Queries
Hugr automatically generates optimized query types for retrieving single records by primary keys and unique constraints. These queries provide the fastest way to fetch individual records when you know their identifying values.
Query by Primary Key
For every data object with a @pk directive, Hugr generates a <object>_by_pk query that retrieves a single record:
query {
customers_by_pk(id: 123) {
id
name
email
phone
created_at
}
}
Response:
{
"data": {
"customers_by_pk": {
"id": 123,
"name": "John Doe",
"email": "john@example.com",
"phone": "+1-555-0123",
"created_at": "2024-01-15T10:30:00Z"
}
}
}
Composite Primary Keys
For tables with multiple primary key fields, all key fields are required:
# Schema definition
type order_details @table(name: "order_details") {
order_id: Int! @pk
product_id: Int! @pk
quantity: Int!
unit_price: Float!
}
Query with composite key:
query {
order_details_by_pk(order_id: 100, product_id: 50) {
order_id
product_id
quantity
unit_price
total: amount # Calculated field
}
}
Non-Existent Records
If no record matches the primary key, the query returns null:
query {
customers_by_pk(id: 99999) {
id
name
}
}
Response:
{
"data": {
"customers_by_pk": null
}
}
Query by Unique Constraint
For each @unique directive on a data object, Hugr generates a query named <object>_by_<field>:
# Schema definition
type customers @table(name: "customers")
@unique(fields: ["email"]) {
id: Int! @pk
name: String!
email: String!
phone: String
}
Query by unique email:
query {
customers_by_email(email: "john@example.com") {
id
name
email
phone
}
}
Multiple Field Unique Constraints
For unique constraints on multiple fields:
# Schema definition
type products @table(name: "products")
@unique(fields: ["sku", "supplier_id"], query_suffix: "sku_supplier") {
id: Int! @pk
sku: String!
supplier_id: Int!
name: String!
price: Float!
}
Query by multiple unique fields:
query {
products_by_sku_supplier(sku: "PROD-123", supplier_id: 5) {
id
name
price
}
}
Multiple Unique Constraints
A data object can have multiple unique constraints, each generating its own query:
type users @table(name: "users")
@unique(fields: ["email"])
@unique(fields: ["username"]) {
id: Int! @pk
email: String!
username: String!
name: String!
}
This generates two queries:
query {
# Query by email
user_by_email: users_by_email(email: "john@example.com") {
id
username
name
}
# Query by username
user_by_username: users_by_username(username: "johndoe") {
id
email
name
}
}
Fetching Related Data
Primary key and unique constraint queries support selecting related data:
query {
customers_by_pk(id: 123) {
id
name
email
# Related orders
orders(
filter: { status: { eq: "pending" } }
order_by: [{ field: "created_at", direction: DESC }]
limit: 5
) {
id
status
total
created_at
}
# Aggregated order statistics
orders_aggregation {
_rows_count
total {
sum
avg
}
}
}
}
Selecting Nested Objects
Include nested object fields and calculated fields:
query {
customers_by_pk(id: 123) {
id
name
email
# Nested object field
address {
street
city
state
postal_code
country
}
# Calculated field
full_address
# Function call field
credit_score(as_of_date: "2024-03-01")
}
}
Using Variables
Use GraphQL variables for dynamic queries:
query GetCustomer($customerId: Int!) {
customers_by_pk(id: $customerId) {
id
name
email
created_at
}
}
Variables:
{
"customerId": 123
}
With unique constraints:
query GetCustomerByEmail($email: String!) {
customers_by_email(email: $email) {
id
name
phone
}
}
Variables:
{
"email": "john@example.com"
}
Parameterized Views
For parameterized views (with @args directive), pass required arguments:
# Schema definition
type monthly_sales @view(
name: "monthly_sales_view"
sql: "SELECT * FROM get_monthly_sales([year], [month])"
) @args(name: "monthly_sales_args", required: true) {
product_id: Int! @pk
product_name: String!
total_sales: Float!
units_sold: Int!
}
input monthly_sales_args {
year: Int!
month: Int!
}
Query with args:
query {
monthly_sales_by_pk(
product_id: 456
args: { year: 2024, month: 3 }
) {
product_id
product_name
total_sales
units_sold
}
}
Soft Deleted Records
For tables with soft delete enabled, deleted records are excluded by default:
query {
customers_by_pk(id: 123) {
id
name
deleted_at # Will be NULL if not deleted
}
}
To include soft deleted records, use the @with_deleted directive:
query {
customers_by_pk(id: 123) @with_deleted {
id
name
deleted_at
}
}
Fragments for Reusability
Use fragments to define reusable selection sets:
fragment CustomerBasicInfo on customers {
id
name
email
phone
}
fragment CustomerWithOrders on customers {
...CustomerBasicInfo
orders(limit: 5) {
id
total
status
}
}
query {
customer1: customers_by_pk(id: 123) {
...CustomerWithOrders
}
customer2: customers_by_email(email: "jane@example.com") {
...CustomerBasicInfo
}
}
Multiple Queries in One Request
Fetch multiple records with aliases:
query {
customer1: customers_by_pk(id: 123) {
id
name
email
}
customer2: customers_by_pk(id: 456) {
id
name
email
}
customer3: customers_by_email(email: "admin@example.com") {
id
name
}
product: products_by_pk(id: 789) {
id
name
price
}
}
Field Selection
Selecting Specific Fields
Always select only the fields you need:
# Good - Select only needed fields
query {
customers_by_pk(id: 123) {
id
name
email
}
}
# Avoid - Selecting unnecessary fields
query {
customers_by_pk(id: 123) {
id
name
email
phone
fax
address
city
state
postal_code
country
region
created_at
updated_at
notes
}
}
Field Aliases
Rename fields in the response:
query {
customers_by_pk(id: 123) {
customerId: id
customerName: name
emailAddress: email
registrationDate: created_at
}
}
Response:
{
"data": {
"customers_by_pk": {
"customerId": 123,
"customerName": "John Doe",
"emailAddress": "john@example.com",
"registrationDate": "2024-01-15T10:30:00Z"
}
}
}
Performance Considerations
1. Use Appropriate Indexes
Ensure indexes exist on:
- Primary key fields (usually automatic)
- Unique constraint fields
- Foreign key fields when selecting relations
2. Limit Related Data
When fetching relations, always use limit:
query {
customers_by_pk(id: 123) {
id
name
orders(limit: 10) { # Limit to prevent large responses
id
total
}
}
}
3. Select Only Required Fields
Avoid selecting large fields unless necessary:
# Avoid selecting large JSON or text fields
query {
customers_by_pk(id: 123) {
id
name
# Skip: large_metadata, detailed_notes, etc.
}
}
4. Use Aggregations Instead
When you only need counts or statistics:
query {
customers_by_pk(id: 123) {
id
name
# Instead of: orders { id }
# Use aggregation:
orders_aggregation {
_rows_count
}
}
}
Common Patterns
Check if Record Exists
query CheckExists($id: Int!) {
customers_by_pk(id: $id) {
id
}
}
If result is null, record doesn't exist.
Get Record with Specific Relations
query GetCustomerWithActiveOrders($id: Int!) {
customers_by_pk(id: $id) {
id
name
email
active_orders: orders(
filter: { status: { in: ["pending", "processing"] } }
) {
id
status
total
}
}
}
Fetch Multiple Related Records
query GetOrderDetails($orderId: Int!) {
orders_by_pk(id: $orderId) {
id
status
total
customer {
id
name
email
}
items: order_details {
product {
id
name
}
quantity
unit_price
}
shipper {
name
phone
}
}
}
Error Handling
Handle cases where records might not exist:
const query = `
query GetCustomer($id: Int!) {
customers_by_pk(id: $id) {
id
name
email
}
}
`;
const result = await client.query(query, { id: 123 });
if (result.data.customers_by_pk === null) {
console.error('Customer not found');
} else {
console.log('Customer:', result.data.customers_by_pk);
}
Next Steps
- Learn about Filtering for complex queries with multiple conditions
- See Relations for working with related data
- Check Aggregations for computing statistics