How to build a RESTful API with Edge Functions and Edge SQL

This guide demonstrates how to build a complete RESTful API for task management using Azion Edge Functions and Edge SQL. You’ll learn to implement full CRUD (Create, Read, Update, Delete) operations at the edge, providing fast, scalable API endpoints globally.

Requirements

Before you begin, ensure you have:

  • An Azion account
  • Azion CLI installed and configured
  • Node.js version 18 or higher
  • pnpm package manager installed
  • Edge SQL enabled in your Azion account
  • Basic understanding of JavaScript, REST APIs, and SQL
  • An Edge SQL instance configured and running

Getting started

Step 1: Set up your development environment

  1. Clone the RESTful tasks example repository:
Terminal window
git clone https://github.com/egermano/edge-functions-examples.git
cd edge-functions-examples/packages/restful-tasks
  1. Install the project dependencies:
Terminal window
pnpm install
  1. Review the project structure to understand the implementation:
Terminal window
ls -la

You should see the main files including the Edge Function implementation, SQL schema, and configuration files.

Step 2: Configure Edge SQL connection

  1. Create a .env file based on the example:
Terminal window
cp .env.example .env
  1. Edit the .env file to include your Edge SQL configuration:
Terminal window
# Edge SQL Configuration
EDGE_SQL_CONNECTION_STRING=your_edge_sql_connection_string
DATABASE_NAME=tasks_db
  1. Get your Edge SQL connection details from Azion Console > Edge SQL.

Step 3: Set up the database schema

  1. Connect to your Edge SQL instance and create the tasks table:
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
completed BOOLEAN DEFAULT FALSE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
  1. Optionally, insert some sample data for testing:
INSERT INTO tasks (title, completed) VALUES
('Complete API documentation', FALSE),
('Test edge function deployment', FALSE),
('Review security configurations', TRUE);

Step 4: Build the project

Compile your Edge Function for deployment:

Terminal window
pnpm build

This command builds your Edge Function and prepares it for deployment to Azion’s edge network.

Step 5: Test locally

Before deploying, test your API locally:

Terminal window
pnpm dev

This starts a local development server where you can test all API endpoints.

API endpoints

The RESTful Tasks API provides the following endpoints:

GET /tasks

Retrieve all tasks from the database.

Response:

{
"tasks": [
{
"id": 1,
"title": "Complete API documentation",
"completed": false,
"created_at": "2023-01-01T10:00:00Z",
"updated_at": "2023-01-01T10:00:00Z"
}
]
}

GET /tasks/

Retrieve a specific task by ID.

Response:

{
"task": {
"id": 1,
"title": "Complete API documentation",
"completed": false,
"created_at": "2023-01-01T10:00:00Z",
"updated_at": "2023-01-01T10:00:00Z"
}
}

POST /tasks

Create a new task.

Request body:

{
"title": "New task title",
"completed": false
}

Response:

{
"task": {
"id": 4,
"title": "New task title",
"completed": false,
"created_at": "2023-01-01T12:00:00Z",
"updated_at": "2023-01-01T12:00:00Z"
}
}

PUT /tasks/

Update an existing task.

Request body:

{
"title": "Updated task title",
"completed": true
}

Response:

{
"task": {
"id": 1,
"title": "Updated task title",
"completed": true,
"created_at": "2023-01-01T10:00:00Z",
"updated_at": "2023-01-01T14:00:00Z"
}
}

DELETE /tasks/

Delete a task by ID.

Response:

{
"message": "Task deleted successfully"
}

Deploying to Azion

Step 1: Authenticate with Azion

  1. Log in to your Azion account via CLI:
Terminal window
azion login
  1. Follow the authentication prompts to connect your CLI with your Azion account.

Step 2: Configure Edge SQL connection

  1. Create secrets for your database connection:
Terminal window
azion create secret EDGE_SQL_CONNECTION_STRING
  1. When prompted, enter your Edge SQL connection string.

Step 3: Deploy the Edge Function

Deploy your RESTful API to Azion’s edge network:

Terminal window
azion deploy

The deployment process will:

  • Upload your Edge Function code
  • Configure the edge application
  • Set up routing rules for all API endpoints
  • Configure database connections
  • Provide you with a unique domain

Step 4: Access your API

After deployment, you’ll receive a domain like https://xxxxxxx.map.azionedge.net. Your RESTful API will be available at this URL within a few minutes after DNS propagation.

Understanding the implementation

Edge Function structure

The RESTful API Edge Function includes:

  1. Router setup: Handling different HTTP methods and routes
  2. Database connection: Connecting to Edge SQL
  3. CRUD operations: Implementing create, read, update, delete operations
  4. Error handling: Managing database and request errors
  5. Response formatting: Returning consistent JSON responses

Database operations example

Here’s how database operations are implemented:

// Get all tasks
async function getAllTasks() {
const query = 'SELECT * FROM tasks ORDER BY created_at DESC';
const result = await database.query(query);
return result.rows;
}
// Create a new task
async function createTask(title, completed = false) {
const query = `
INSERT INTO tasks (title, completed)
VALUES (?, ?)
RETURNING *
`;
const result = await database.query(query, [title, completed]);
return result.rows[0];
}
// Update a task
async function updateTask(id, updates) {
const { title, completed } = updates;
const query = `
UPDATE tasks
SET title = ?, completed = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
RETURNING *
`;
const result = await database.query(query, [title, completed, id]);
return result.rows[0];
}

Key benefits

  • Performance: API processing at the edge reduces latency
  • Scalability: Automatically scales with demand
  • Global distribution: Edge locations provide worldwide coverage
  • Data consistency: Edge SQL ensures ACID compliance
  • Cost efficiency: Optimized data access and transfer

Testing your RESTful API

Step 1: Test CRUD operations

  1. Create tasks: Test POST /tasks endpoint
  2. Read tasks: Test GET /tasks and GET /tasks/
    endpoints
  3. Update tasks: Test PUT /tasks/
    endpoint
  4. Delete tasks: Test DELETE /tasks/
    endpoint

Step 2: Test error handling

  1. Invalid data: Test with malformed request bodies
  2. Non-existent resources: Test with invalid task IDs
  3. Database errors: Test error handling for connection issues
  4. Validation errors: Test input validation logic

Step 3: Performance testing

  1. Response times: Measure API response times
  2. Concurrent requests: Test multiple simultaneous requests
  3. Database performance: Monitor SQL query performance
  4. Load testing: Test with high request volumes

Example API usage

Using cURL

Terminal window
# Get all tasks
curl -X GET https://your-domain.map.azionedge.net/tasks
# Create a new task
curl -X POST https://your-domain.map.azionedge.net/tasks \
-H "Content-Type: application/json" \
-d '{"title": "Learn Azion Edge Functions", "completed": false}'
# Update a task
curl -X PUT https://your-domain.map.azionedge.net/tasks/1 \
-H "Content-Type: application/json" \
-d '{"title": "Updated task title", "completed": true}'
# Delete a task
curl -X DELETE https://your-domain.map.azionedge.net/tasks/1

Using JavaScript fetch

// Get all tasks
const tasks = await fetch('/tasks').then(r => r.json());
// Create a new task
const newTask = await fetch('/tasks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: 'New task',
completed: false
})
}).then(r => r.json());
// Update a task
const updatedTask = await fetch('/tasks/1', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: 'Updated task',
completed: true
})
}).then(r => r.json());

Advanced features

Adding authentication

Implement authentication to secure your API:

async function authenticateRequest(request) {
const authHeader = request.headers.get('Authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new Error('Authentication required');
}
const token = authHeader.substring(7);
return await validateToken(token);
}

Input validation

Add comprehensive input validation:

function validateTaskData(data) {
const errors = [];
if (!data.title || typeof data.title !== 'string') {
errors.push('Title is required and must be a string');
}
if (data.title && data.title.length > 255) {
errors.push('Title must be less than 255 characters');
}
if (data.completed !== undefined && typeof data.completed !== 'boolean') {
errors.push('Completed must be a boolean');
}
return errors;
}

Pagination

Implement pagination for large datasets:

async function getTasksPaginated(page = 1, limit = 10) {
const offset = (page - 1) * limit;
const query = `
SELECT * FROM tasks
ORDER BY created_at DESC
LIMIT ? OFFSET ?
`;
const tasks = await database.query(query, [limit, offset]);
const countQuery = 'SELECT COUNT(*) as total FROM tasks';
const totalResult = await database.query(countQuery);
const total = totalResult.rows[0].total;
return {
tasks: tasks.rows,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit)
}
};
}

Troubleshooting

Common issues and solutions

  • Database connection errors: Verify Edge SQL credentials and connection string
  • API endpoint not found: Check routing configuration and deployment
  • SQL errors: Validate database schema and query syntax
  • Performance issues: Optimize SQL queries and database indexing

Debugging tips

  1. Enable logging: Add comprehensive logging for debugging
  2. Test locally: Use local development server to isolate issues
  3. Monitor performance: Track API response times and database queries
  4. Error handling: Implement proper error responses and status codes

Next steps

  • Implement advanced filtering and sorting
  • Add real-time updates with WebSockets
  • Integrate with authentication providers
  • Implement API rate limiting and caching
  • Add comprehensive API documentation with OpenAPI/Swagger