メインコンテンツまでスキップ
バージョン: 🚧 Canary

📍 Location Service

Testing Status

The Location Service provides a complete REST API for managing hierarchical locations with parent-child relationships and ancestor tracking. It's designed to handle organizational structures, geographic hierarchies, and location-based data using the Nodeblocks functional composition approach and MongoDB integration.


🚀 Quickstart

import express from 'express';
import { middlewares, services, drivers } from '@nodeblocks/backend-sdk';

const { nodeBlocksErrorMiddleware } = middlewares;
const { locationService } = services;
const { withMongo } = drivers;

const connectToDatabase = withMongo('mongodb://localhost:27017', 'dev', 'user', 'password');

express()
.use(
locationService(
{
...(await connectToDatabase('locations')),
...(await connectToDatabase('identities')),
},
{
authSecrets: {
authEncSecret: 'your-encryption-secret',
authSignSecret: 'your-signing-secret',
},
identity: {
typeIds: {
admin: '100',
guest: '000',
regular: '001',
},
},
}
)
)
.use(nodeBlocksErrorMiddleware())
.listen(8089, () => console.log('Server running'));

📋 Endpoint Summary

Location Operations

MethodPathDescriptionAuth Required
POST/locationsCreate a new location✅ Admin
GET/locations/:locationIdRetrieve a location by ID
GET/locationsList all locations
PATCH/locations/:locationIdUpdate a location✅ Admin
DELETE/locations/:locationIdDelete a location✅ Admin

🗄️ Entity Schema

The location entity combines base fields (auto-generated) with location-specific data:

{
"name": "string",
"code": "string",
"type": "string",
"parentId": "string",
"ancestors": ["string"],
"createdAt": "string (datetime)",
"id": "string",
"updatedAt": "string (datetime)"
}

Field Details

FieldTypeAuto-GeneratedRequiredDescription
namestringLocation name
codestringUnique location identifier code
typestringLocation type classification (ORGANIZATION, REGION, CITY, BUILDING)
parentIdstringParent location ID for hierarchical structure
ancestorsstring[]Array of ancestor location IDs (auto-calculated)
createdAtdatetimeCreation timestamp
idstringUnique identifier (UUID)
updatedAtdatetimeLast modification timestamp

📝 Note: Auto-generated fields are set by the service and should not be included in create/update requests. The parentId field enables hierarchical location structures, and ancestors is automatically calculated from the parent hierarchy.


🔐 Authentication Headers

For protected endpoints, include the following headers:

Authorization: Bearer <admin_access_token>
x-nb-fingerprint: <device_fingerprint>

⚠️ Important: The x-nb-fingerprint header is required for all authenticated requests if fingerprint was specified during authorization. Without it, requests will return 401 Unauthorized.


🔧 API Endpoints

1. Create Location

Creates a new location with the provided information and optional hierarchical parent relationship.

Request:

  • Method: POST
  • Path: /locations
  • Headers:
    • Content-Type: application/json
    • Authorization: Bearer <token>
    • x-nb-fingerprint: <device-fingerprint>
  • Authorization: Bearer token required (admin)

Request Body:

FieldTypeRequiredDescription
namestringLocation name
codestringUnique location identifier code
typestringLocation type classification
parentIdstringParent location ID for hierarchical structure

Response Body:

FieldTypeDescription
idstringUnique location identifier
namestringLocation name
codestringUnique location identifier code
typestringLocation type classification
parentIdstringParent location ID (if applicable)
ancestorsstring[]Array of ancestor location IDs
createdAtstringCreation timestamp
updatedAtstringLast update timestamp

Validation:

  • Schema Validation: Enforced automatically (name, code, type required)
  • Route Validators:
    • Require authenticated request (bearer token)
    • Require admin role

Example Request:

curl -X POST http://localhost:8089/locations \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <admin_token>" \
-H "x-nb-fingerprint: <device-fingerprint>" \
-d '{
"name": "Headquarters",
"code": "HQ",
"type": "BUILDING",
"parentId": "city-123"
}'

Success Response:

HTTP/1.1 201 Created
Content-Type: application/json

{
"id": "location-uuid",
"name": "Headquarters",
"code": "HQ",
"type": "BUILDING",
"parentId": "city-123",
"ancestors": ["org-456", "region-789", "city-123"],
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}

2. List Locations

Retrieves a paginated list of locations with public access and pagination support.

Request:

  • Method: GET
  • Path: /locations
  • Authorization: None required

Query Parameters:

ParameterTypeRequiredDescription
pagenumberPage number for pagination (1-1000)
limitnumberNumber of items per page (1-50)

Response Body: Paginated response with locations array and metadata.

Response Structure:

{
"data": [
{
"id": "string",
"name": "string",
"code": "string",
"type": "string",
"parentId": "string",
"ancestors": ["string"],
"createdAt": "string",
"updatedAt": "string"
}
],
"metadata": {
"pagination": {
"page": number,
"limit": number,
"total": number,
"totalPages": number,
"hasNext": boolean,
"hasPrev": boolean
}
}
}

Validation:

  • Schema Validation: Query parameter validation for pagination (page, limit)
  • Route Validators: None

Example Request:

curl "http://localhost:8089/locations?page=1&limit=10"

Success Response:

HTTP/1.1 200 OK
Content-Type: application/json

{
"data": [
{
"id": "location-uuid",
"name": "Headquarters",
"code": "HQ",
"type": "BUILDING",
"parentId": "city-123",
"ancestors": ["org-456", "region-789", "city-123"],
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}
],
"metadata": {
"pagination": {
"page": 1,
"limit": 10,
"total": 25,
"totalPages": 3,
"hasNext": true,
"hasPrev": false
}
}
}

3. Get Location by ID

Retrieves a location by ID with public access.

Request:

  • Method: GET
  • Path: /locations/:locationId
  • Authorization: None required

URL Parameters:

ParameterTypeRequiredDescription
locationIdstringUnique location identifier

Response Body:

FieldTypeDescription
idstringUnique location identifier
namestringLocation name
codestringUnique location identifier code
typestringLocation type classification
parentIdstringParent location ID (if applicable)
ancestorsstring[]Array of ancestor location IDs
createdAtstringCreation timestamp
updatedAtstringLast update timestamp

Validation:

  • Schema Validation: Path parameter validation for locationId
  • Route Validators: None

Example Request:

curl http://localhost:8089/locations/loc-123

Success Response:

HTTP/1.1 200 OK
Content-Type: application/json

{
"id": "loc-123",
"name": "San Francisco",
"code": "SF",
"type": "CITY",
"parentId": "region-456",
"ancestors": ["org-789", "region-456"],
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}

4. Update Location

Updates an existing location with partial field modifications and admin authentication.

Request:

  • Method: PATCH
  • Path: /locations/:locationId
  • Headers: Content-Type: application/json
  • Authorization: Required (Admin)

URL Parameters:

ParameterTypeRequiredDescription
locationIdstringUnique location identifier

Request Body (all fields optional):

FieldTypeRequiredDescription
namestringLocation name
codestringUnique location identifier code
typestringLocation type classification

Response Body:

FieldTypeDescription
idstringUnique location identifier
namestringUpdated location name
codestringUpdated location identifier code
typestringUpdated location type
parentIdstringParent location ID (unchanged)
ancestorsstring[]Array of ancestor location IDs (unchanged)
createdAtstringCreation timestamp
updatedAtstringLast update timestamp

Validation:

  • Schema Validation: Enforced automatically (partial updates, limited fields allowed)
  • Route Validators:
    • Require authenticated request (bearer token)
    • Require admin role

Example Request:

curl -X PATCH http://localhost:8089/locations/loc-123 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <admin_token>" \
-H "x-nb-fingerprint: <device-fingerprint>" \
-d '{
"name": "Updated Headquarters Name",
"code": "HQ-UPDATED"
}'

Success Response:

HTTP/1.1 200 OK
Content-Type: application/json

{
"id": "loc-123",
"name": "Updated Headquarters Name",
"code": "HQ-UPDATED",
"type": "BUILDING",
"parentId": "region-456",
"ancestors": ["org-789", "region-456"],
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-16T14:20:00Z"
}

5. Delete Location

Deletes an existing location with comprehensive hierarchy validation to prevent orphaned child locations.

Request:

  • Method: DELETE
  • Path: /locations/:locationId
  • Authorization: Required (Admin)

URL Parameters:

ParameterTypeRequiredDescription
locationIdstringUnique location identifier

Response Body:

FieldTypeDescription
No response body-Delete endpoint returns no response body on success

Validation:

  • Schema Validation: Path parameter validation for locationId
  • Route Validators:
    • Require authenticated request (bearer token)
    • Require admin role
    • Validates no descendant locations exist (prevents deletion if children exist)

Example Request:

curl -X DELETE http://localhost:8089/locations/loc-123 \
-H "Authorization: Bearer <admin_token>" \
-H "x-nb-fingerprint: <device-fingerprint>"

Success Response:

HTTP/1.1 204 No Content

Error Responses:

When location has descendant locations:

HTTP/1.1 409 Conflict
Content-Type: application/json

{
"error": {
"message": "Location has descendant locations"
}
}

When location not found:

HTTP/1.1 404 Not Found
Content-Type: application/json

{
"error": {
"message": "Location not found"
}
}

Location Types

The service supports various location type classifications:

TypeDescriptionExample
ORGANIZATIONRoot-level organizational entitiesGlobal Corp, Acme Inc
REGIONGeographic or administrative regionsWest Coast, EMEA, APAC
CITYMunicipal or urban areasNew York, Tokyo, London
BUILDINGPhysical structures or facilitiesHQ Building, Branch Office

Hierarchical Relationships

Parent-Child Relationships

Locations can have parent-child relationships:

// Create organization (no parent)
const org = await createLocation({
name: 'Tech Corp',
code: 'TECH',
type: 'ORGANIZATION'
});

// Create region under organization
const region = await createLocation({
name: 'West Coast',
code: 'WEST',
type: 'REGION',
parentId: org.id
});

// Create city under region
const city = await createLocation({
name: 'San Francisco',
code: 'SF',
type: 'CITY',
parentId: region.id
});

Ancestor Chains

Each location maintains a complete ancestor chain for efficient hierarchical queries:

{
"id": "building-uuid",
"name": "Headquarters",
"code": "HQ",
"type": "BUILDING",
"parentId": "city-uuid",
"ancestors": ["org-uuid", "region-uuid", "city-uuid"],
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}

Authorization & Security

Admin-Only Access

Currently, all location operations require admin privileges:

// Service configuration with admin identity type
const config = {
authSecrets: {
authEncSecret: 'your-encryption-secret',
authSignSecret: 'your-signing-secret'
},
identity: {
typeIds: {
admin: '100', // Admin users
guest: '000', // Guest users
regular: '001' // Regular users
}
}
};

Future Authorization Enhancements

Planned Features:

  • Organization-based access control
  • Location-type specific permissions
  • Email verification requirements
  • Hierarchical permission inheritance

Usage Examples

Basic Setup

import express from 'express';
import { locationService } from '@nodeblocks/backend-sdk';
import { withMongo } from '@nodeblocks/backend-sdk/drivers';

const app = express();

// Database setup
const connectToDatabase = withMongo('mongodb://localhost:27017', 'dev', 'user', 'password');

// Service configuration
const locationServiceConfig = {
authSecrets: {
authEncSecret: process.env.AUTH_ENC_SECRET,
authSignSecret: process.env.AUTH_SIGN_SECRET
},
identity: {
typeIds: {
admin: '100',
guest: '000',
regular: '001'
}
}
};

// Data stores
const dataStores = {
...(await connectToDatabase('identities')),
...(await connectToDatabase('locations'))
};

// Mount service
app.use('/api/locations', locationService(dataStores, locationServiceConfig));

Creating Location Hierarchies

// 1. Create root organization
const orgResponse = await fetch('/api/locations', {
method: 'POST',
headers: {
'Authorization': `Bearer ${adminToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Global Enterprises',
code: 'GLOBAL',
type: 'ORGANIZATION'
})
});

// 2. Create regional division
const regionResponse = await fetch('/api/locations', {
method: 'POST',
headers: {
'Authorization': `Bearer ${adminToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'North America',
code: 'NA',
type: 'REGION',
parentId: orgResponse.body.id
})
});

// 3. Create city location
const cityResponse = await fetch('/api/locations', {
method: 'POST',
headers: {
'Authorization': `Bearer ${adminToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'New York',
code: 'NYC',
type: 'CITY',
parentId: regionResponse.body.id
})
});

// 4. Create building
const buildingResponse = await fetch('/api/locations', {
method: 'POST',
headers: {
'Authorization': `Bearer ${adminToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Manhattan Office',
code: 'MANHATTAN',
type: 'BUILDING',
parentId: cityResponse.body.id
})
});

console.log('Created hierarchy:');
console.log('Organization -> Region -> City -> Building');

Service Architecture

Feature Composition

The location service uses feature composition for modularity:

export const locationService: LocationService = (dataStores, configuration) => {
return defService(
partial(compose(createLocationFeature), [{ configuration, dataStores }])
);
};

Middleware Integration

The service integrates with standard Nodeblocks middleware:

  • Authentication: Bearer token validation
  • Authorization: Identity type checking
  • Error Handling: Structured error responses
  • Request Logging: Comprehensive request tracking

Database Considerations

For optimal performance, create these indexes on the locations collection:

// Unique index on location ID
db.locations.createIndex({ id: 1 }, { unique: true });

// Index for parent-child queries
db.locations.createIndex({ parentId: 1 });

// Index for ancestor chain queries
db.locations.createIndex({ ancestors: 1 });

// Compound index for hierarchical queries
db.locations.createIndex({ type: 1, ancestors: 1 });

// Index for code lookups
db.locations.createIndex({ code: 1 }, { unique: true });

Data Consistency

The service ensures data consistency through:

  • Atomic Operations: Single transaction for location creation
  • Referential Integrity: Parent validation before creation
  • Ancestor Accuracy: Automatic ancestor chain calculation
  • Unique Constraints: Code uniqueness enforcement

Error Handling

Common Error Responses

Validation Errors (400)

{
"error": {
"data": [
"request body must have required property 'name'",
"request body must have required property 'code'"
],
"message": "Validation Error"
}
}

Authorization Errors (403)

{
"error": {
"message": "Forbidden"
}
}

Not Found Errors (404)

{
"error": {
"message": "Location not found"
}
}

Database Errors (500)

{
"error": {
"message": "Failed to create location"
}
}

Future Enhancements

Planned Features

  • Location Retrieval: GET endpoints for location queries
  • Location Updates: PATCH endpoints for location modifications
  • Location Deletion: DELETE endpoints with cascade options
  • Bulk Operations: Bulk creation, update, and deletion
  • Advanced Queries: Filtering by type, hierarchy, and custom criteria
  • Geographic Features: Coordinate support and geographic queries
  • Import/Export: CSV and JSON import/export capabilities

Extended Authorization

  • Role-Based Access: Location-specific permissions
  • Organization Scoping: Organization-based location access
  • Hierarchical Permissions: Permission inheritance through location trees

Migration & Setup

Database Setup

// Create locations collection
db.createCollection('locations');

// Create indexes
db.locations.createIndex({ id: 1 }, { unique: true });
db.locations.createIndex({ parentId: 1 });
db.locations.createIndex({ ancestors: 1 });
db.locations.createIndex({ type: 1, ancestors: 1 });
db.locations.createIndex({ code: 1 }, { unique: true });

Application Integration

// Add to main application
import { locationService } from '@nodeblocks/backend-sdk';

// Configure and mount
const locationConfig = { /* configuration */ };
const locationDataStores = { /* data stores */ };

app.use('/api/locations', locationService(locationDataStores, locationConfig));

Testing

Service Testing

import { locationService } from '@nodeblocks/backend-sdk';
import request from 'supertest';

// Test service integration
describe('Location Service', () => {
it('Should create locations with proper hierarchy', async () => {
const app = express();
app.use('/locations', locationService(dataStores, config));

const response = await request(app)
.post('/locations')
.set('Authorization', `Bearer ${adminToken}`)
.send({
name: 'Test Location',
code: 'TEST',
type: 'BUILDING'
});

expect(response.status).toBe(201);
expect(response.body.ancestors).toEqual([]);
});
});

The Location Service provides a solid foundation for hierarchical location management with room for future expansion and enhanced functionality.