HTTP server for serving @targetd/api targeting data over REST endpoints.
| JS Runtime | Command |
|---|---|
| Node.js | npx jsr add @targetd/api @targetd/server |
| Bun | bunx jsr add @targetd/api @targetd/server |
| Deno | deno add jsr:@targetd/api jsr:@targetd/server |
@targetd/server creates an Express-based HTTP server that exposes your
@targetd/api Data instance via REST endpoints.
Clients can query targeted payloads using query parameters.
Key features:
- REST API: Standard HTTP endpoints for querying data
- Query parameters: Pass targeting queries via URL parameters
- CORS enabled: Ready for cross-origin requests
- Type-safe: Works seamlessly with @targetd/client
- Express-based: Built on Express for easy integration and middleware support
import { Data, DataSchema, targetIncludes } from '@targetd/api'
import { createServer } from '@targetd/server'
import { z } from 'zod'
const schema = DataSchema.create()
.usePayload({
greeting: z.string(),
config: z.object({
enabled: z.boolean(),
}),
})
.useTargeting({
country: targetIncludes(z.string()),
})
const data = await Data.create(schema)
.addRules('greeting', [
{
targeting: { country: ['US'] },
payload: 'Hello!',
},
{
targeting: { country: ['ES'] },
payload: '¡Hola!',
},
{
payload: 'Hi!',
},
])
.addRules('config', [
{
payload: { enabled: true },
},
])
const server = createServer(data)
server.listen(3000, () => {
console.log('Server running on port 3000')
})The server exposes three main endpoints:
Get a single payload by name with optional query parameters.
Example requests:
# Get default payload
curl http://localhost:3000/greeting
# Get targeted payload
curl http://localhost:3000/greeting?country=USResponses:
200 OK: Returns the matched payload as JSON204 No Content: Payload exists but no rule matched404 Not Found: Payload name doesn't exist
Get all payloads at once with optional query parameters.
Example requests:
# Get all default payloads
curl http://localhost:3000/
# Get all payloads with targeting
curl http://localhost:3000/?country=USResponse:
{
"greeting": "Hello!",
"config": {
"enabled": true
}
}Create custom routes using path parameters instead of query parameters.
Example:
const server = createServer(data, {
pathStructure: ['country', 'device'],
})
server.listen(3000)Request:
curl http://localhost:3000/US/mobileThis is equivalent to: /?country=US&device=mobile
Creates an Express server with targeting endpoints.
Parameters:
data: Data instance or function returning Data (for dynamic data)options(optional):app: Existing Express app to extend (creates new one if not provided)pathStructure: Array of query parameter names to use as path segments
Returns: Express application instance
import { Data, DataSchema, targetEquals, targetIncludes } from '@targetd/api'
import { createServer } from '@targetd/server'
import { z } from 'zod'
const schema = DataSchema.create()
.usePayload({
banner: z.string(),
feature: z.object({
enabled: z.boolean(),
maxUsers: z.number(),
}),
})
.useTargeting({
platform: targetIncludes(z.string()),
isPremium: targetEquals(z.boolean()),
})
const data = await Data.create(schema)
.addRules('banner', [
{
targeting: { platform: ['mobile'] },
payload: '📱 Mobile Banner',
},
{
targeting: { platform: ['desktop'] },
payload: '🖥 Desktop Banner',
},
{
payload: 'Default Banner',
},
])
.addRules('feature', [
{
targeting: { isPremium: true },
payload: { enabled: true, maxUsers: 1000 },
},
{
payload: { enabled: true, maxUsers: 10 },
},
])
createServer(data).listen(3000)Usage:
# Get mobile banner
curl http://localhost:3000/banner?platform=mobile
# Returns: "📱 Mobile Banner"
# Get feature config for premium users
curl http://localhost:3000/feature?isPremium=true
# Returns: {"enabled": true, "maxUsers": 1000}
# Get all payloads for mobile platform
curl http://localhost:3000/?platform=mobile
# Returns: {"banner": "📱 Mobile Banner", "feature": {"enabled": true, "maxUsers": 10}}Use a function to provide data for dynamic updates:
import { Data, DataSchema } from '@targetd/api'
import { watch } from '@targetd/fs'
import { createServer } from '@targetd/server'
const baseData = await Data.create(
DataSchema.create()
.usePayload({ content: z.string() }),
)
let currentData = baseData
// Watch for file changes
watch(baseData, './rules', (error, updatedData) => {
if (!error) {
currentData = updatedData
console.log('Rules updated')
}
})
// Server always uses latest data
const server = createServer(() => currentData)
server.listen(3000)Create REST-friendly URLs using path parameters:
import { Data, DataSchema, targetIncludes } from '@targetd/api'
import { createServer } from '@targetd/server'
import { z } from 'zod'
const schema = DataSchema.create()
.usePayload({
content: z.string(),
})
.useTargeting({
region: targetIncludes(z.string()),
language: targetIncludes(z.string()),
})
const data = await Data.create(schema).addRules('content', [
{
targeting: {
region: ['US'],
language: ['en'],
},
payload: 'US English content',
},
{
targeting: {
region: ['US'],
language: ['es'],
},
payload: 'US Spanish content',
},
{
payload: 'Default content',
},
])
const server = createServer(data, {
pathStructure: ['region', 'language'],
})
server.listen(3000)Usage:
# RESTful URLs
curl http://localhost:3000/US/en
# Returns: {"content": "US English content"}
curl http://localhost:3000/US/es
# Returns: {"content": "US Spanish content"}Integrate into an existing Express application:
import express from 'express'
import { Data, DataSchema } from '@targetd/api'
import { createServer } from '@targetd/server'
const app = express()
// Your existing routes
app.get('/health', (req, res) => {
res.json({ status: 'ok' })
})
// Add targetd endpoints
const data = await Data.create(
DataSchema.create()
.usePayload({ config: z.object({ version: z.string() }) }),
).addRules('config', [{ payload: { version: '1.0.0' } }])
createServer(data, { app })
app.listen(3000)Now you have both your custom routes and targetd endpoints on the same server.
Combine with @targetd/date-range for time-based content:
import { Data, DataSchema } from '@targetd/api'
import { createServer } from '@targetd/server'
import dateRangeTargeting from '@targetd/date-range'
import { z } from 'zod'
const schema = DataSchema.create()
.usePayload({
campaign: z.string(),
})
.useTargeting({
date: dateRangeTargeting,
})
const data = await Data.create(schema).addRules('campaign', [
{
targeting: {
date: {
start: '2024-12-01',
end: '2024-12-31',
},
},
payload: '🎄 Holiday Campaign',
},
{
payload: 'Regular Campaign',
},
])
createServer(data).listen(3000)Usage:
# Automatic current date evaluation
curl http://localhost:3000/campaign
# Historical query
curl 'http://localhost:3000/campaign?date[start]=2024-12-15'Handle nested and array query parameters:
import { Data, DataSchema, targetIncludes } from '@targetd/api'
import { createServer } from '@targetd/server'
import { z } from 'zod'
const schema = DataSchema.create()
.usePayload({
recommendations: z.array(z.string()),
})
.useTargeting({
interests: targetIncludes(z.string()),
})
const data = await Data.create(schema).addRules('recommendations', [
{
targeting: { interests: ['sports'] },
payload: ['Football News', 'Basketball Scores'],
},
{
targeting: { interests: ['tech'] },
payload: ['Latest Gadgets', 'Programming Tips'],
},
{
payload: ['General News'],
},
])
createServer(data).listen(3000)Usage:
# Query with interests
curl 'http://localhost:3000/recommendations?interests=sports'
# Returns: ["Football News", "Basketball Scores"]
# Get all with interests
curl 'http://localhost:3000/?interests=tech'
# Returns: {"recommendations": ["Latest Gadgets", "Programming Tips"]}The server includes built-in error handling:
Returned when query parameters fail validation:
curl 'http://localhost:3000/content?invalidParam=value'Response:
{
"name": "$ZodError",
"message": "[{\"code\":\"unrecognized_keys\",\"path\":[],\"message\":\"Unrecognized key(s) in object: 'invalidParam'\"}]"
}Returned when payload name doesn't exist:
curl http://localhost:3000/nonexistentResponse:
{
"name": "StatusError",
"message": "Unknown data property nonexistent"
}Returned when payload exists but no rule matched:
// If no rule matches and no default rule exists
curl http://localhost:3000/content?neverMatchingCondition=true
// Returns: 204 No Content (empty response)CORS is enabled by default. For custom CORS configuration, use an existing Express app:
import express from 'express'
import cors from 'cors'
import { createServer } from '@targetd/server'
const app = express()
// Custom CORS
app.use(cors({
origin: 'https://example.com',
credentials: true,
}))
createServer(data, { app })The server is designed to work seamlessly with @targetd/client:
Server:
import { Data, DataSchema, targetIncludes } from '@targetd/api'
import { createServer } from '@targetd/server'
const schema = DataSchema.create()
.usePayload({ greeting: z.string() })
.useTargeting({ country: targetIncludes(z.string()) })
export const data = await Data.create(schema).addRules('greeting', [
{ targeting: { country: ['US'] }, payload: 'Hello!' },
{ payload: 'Hi!' },
])
createServer(data).listen(3000)Client:
import { Client } from '@targetd/client'
import { data } from './server.ts' // Share type definition
const client = new Client('http://localhost:3000', data)
const greeting = await client.getPayload('greeting', { country: 'US' })
// Fully type-safe!import { Data } from '@targetd/api'
import { load } from '@targetd/fs'
import { createServer } from '@targetd/server'
const data = await load(
baseData,
process.env.RULES_DIR || './rules',
)
const port = process.env.PORT || 3000
createServer(data).listen(port, () => {
console.log(`Server listening on port ${port}`)
})FROM node:20
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]- Use functions for dynamic data: Pass a function to
createServer()when data changes - Share type definitions: Export and reuse Data definitions between server and client
- Error handling: Server includes comprehensive error handling by default
- Path structure: Use
pathStructurefor REST-friendly URLs - CORS: Default CORS works for most cases, customize if needed
- Environment variables: Configure port and paths via environment variables
- @targetd/api - Core targeting and data querying API
- @targetd/client - Type-safe HTTP client for querying servers
- @targetd/fs - Load rules from JSON/YAML files
- @targetd/date-range - Date range targeting descriptor
See LICENSE file for details.