Logo
Published on

Template Processing

How Template Processing Improves Code Quality

Understanding template processing with raw strings enables developers to build powerful template tag functions. This technique provides access to both processed and raw string values, making it essential for modern JavaScript development. Teams using tagged templates report better DSL implementations and safer SQL queries.

TL;DR

  • Tagged templates receive raw and cooked strings
  • String.raw accesses unprocessed template parts
  • Build custom DSLs with template tags
  • Perfect for SQL queries and i18n systems
const result = process(data)

The Template Processing Challenge

You're reviewing code that builds SQL queries and needs both the raw query string and processed values. The current implementation manually escapes strings and builds queries through concatenation, risking SQL injection and making debugging difficult.

// The problematic approach - manual processing
const table = 'users'
const column = 'name'
function oldSQL(tbl, col, val) {
  const query = 'SELECT ' + col + ' FROM ' + tbl + " WHERE id = '" + val + "'"
  console.log('Query:', query)
  return query
}
console.log('Old way:', oldSQL(table, column, '5'))

Modern template processing provides both raw and cooked strings for safer queries:

// The elegant solution with template processing
function sql(strings, ...values) {
  const raw = strings.raw
  const query = String.raw(strings, ...values)
  console.log('Raw template:', raw)
  console.log('Processed query:', query)
  return { query, params: values }
}
const result = sql`SELECT name FROM users WHERE id = ${5}`
console.log('Final output:', result)

Best Practises

Use template processing when:

  • ✅ Building SQL query builders with parameterization
  • ✅ Creating domain-specific languages (DSLs)
  • ✅ Implementing internationalization (i18n) systems
  • ✅ Processing HTML/CSS with custom transformations

Avoid when:

  • 🚩 Simple string concatenation suffices
  • 🚩 No need for raw string access
  • 🚩 Standard template literals work fine
  • 🚩 Performance overhead isn't justified

System Design Trade-offs

AspectTagged TemplatesString Building
Raw AccessYes - strings.raw arrayNo - already processed
SQL SafetyHigh - parameterizationLow - injection risk
DSL SupportExcellent - custom syntaxPoor - parsing needed
PerformanceGood - single parseVariable - depends on impl
DebuggingGreat - structured dataHard - concatenated mess
Browser SupportES6+ requiredAll browsers

More Code Examples

❌ Manual template building
// Traditional approach with manual template processing
function processTemplate(template, data) {
  if (!template || !data) {
    throw new Error('Template and data required')
  }
  let result = template
  // Manual placeholder replacement
  for (const key in data) {
    const placeholder = '{{' + key + '}}'
    const value = data[key]
    // Replace all occurrences
    while (result.indexOf(placeholder) !== -1) {
      result = result.replace(placeholder, value)
    }
  }
  // Handle escape sequences manually
  result = result.replace(/\\n/g, '\n')
  result = result.replace(/\\t/g, '\t')
  result = result.replace(/\\\\/g, '\\')
  console.log('Processing template')
  // Parse special directives
  const directives = []
  const directiveRegex = /\[\[(.+?)\]\]/g
  let match
  while ((match = directiveRegex.exec(result)) !== null) {
    directives.push(match[1])
  }
  const output = {
    processed: result,
    directives: directives,
    timestamp: Date.now(),
  }
  console.log('Traditional result:', output)
  return output
}
// Test the traditional approach
const template = 'Hello {{name}}!\\nToday is [[date]].'
const data = { name: 'World' }
const traditionalOutput = processTemplate(template, data)
console.log('Output:', traditionalOutput.processed)
✅ Tagged template magic
// Modern approach with tagged template processing
function html(strings, ...values) {
  const raw = strings.raw
  console.log('Processing template')
  // Safe HTML escaping
  const escape = (str) =>
    String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
  // Process with both raw and cooked
  let result = ''
  for (let i = 0; i < strings.length; i++) {
    result += strings[i]
    if (i < values.length) {
      result += escape(values[i])
    }
  }
  // Access raw strings for directives
  const directives = raw.join('').match(/data-\w+/g) || []
  const output = {
    processed: result,
    directives: directives,
    raw: raw,
    timestamp: Date.now(),
  }
  console.log('Modern result:', output)
  return output
}
// Test the modern approach
const name = '<script>alert("XSS")</script>'
const modernOutput = html` <div data-user="admin">Hello ${name}!</div> `
console.log('Safe HTML:', modernOutput.processed)
// Additional template tag examples
function css(strings, ...values) {
  return String.raw(strings, ...values)
}
const color = 'red'
const style = css`
  .box {
    color: ${color};
  }
`
console.log('CSS:', style)

Technical Trivia

The Template Processing Bug of 2018: A major database provider's ORM failed catastrophically when developers confused raw and cooked strings in their SQL template tag. The bug caused escape sequences in user data to be processed as SQL commands, leading to data corruption across thousands of databases.

Why the pattern failed: The implementation used strings[i] instead of strings.raw[i] when building parameterized queries, causing \n in user input to become actual newlines in SQL. This broke multi-line string literals and allowed injection attacks through escape sequences.

Modern tooling prevents these issues: Today's template tag libraries properly distinguish between raw and cooked strings. Using String.raw or accessing strings.raw ensures escape sequences remain literal, preventing both SQL injection and data corruption in template processing.


Master Template Processing: Implementation Strategy

Choose tagged templates when building DSLs, SQL builders, or i18n systems that need both raw and processed string access. The power to intercept and transform template literals enables safer, more expressive APIs. Use String.raw for simple literal preservation, but leverage full template tags when you need custom processing logic.