Animated Circular Progress Bar0
Font.Lokio
CLI

Template Syntax

Complete guide to EJS syntax that can be used in template files


Template Syntax#

Lokio templates use the EJS (Embedded JavaScript) format. You can insert variables, conditions, and loops directly inside template code.


Basics: Inserting Variables#

Use <%= %> to insert a value:

// Template
export class <%= name %> {}
 
// Input: name = "UserProfile"
// Result:
export class UserProfile {}

Casing Transformations#

This is the most commonly used feature. Lokio provides functions to automatically transform variable formats.

Full Reference Table#

FunctionInputOutput
<%= name %>user profileuser profile (as-is)
<%= Name %>user profileUser profile (capitalize shortcut)
<%= capitalize(name) %>user profileUser profile
<%= camelCase(name) %>user profileuserProfile
<%= pascalCase(name) %>user profileUserProfile
<%= kebabCase(name) %>user profileuser-profile
<%= snakeCase(name) %>user profileuser_profile
<%= constantCase(name) %>user profileUSER_PROFILE
<%= dotCase(name) %>user profileuser.profile
<%= pathCase(name) %>user profileuser/profile
<%= titleCase(name) %>user profileUser Profile
<%= sentenceCase(name) %>user profileUser profile
<%= noSpace(name) %>user profileuserprofile
<%= lowercase(name) %>User Profileuser profile
<%= uppercase(name) %>user profileUSER PROFILE
<%= plural(name) %>userusers
<%= singular(name) %>usersuser

Tip: Input can be in any format — userProfile, user-profile, user_profile, or User Profile — all will be recognized and converted correctly.

Real-World Example#

// Template (input: name = "product order")
 
import { Injectable } from '@nestjs/common';
 
// pascalCase → ProductOrderService
@Injectable()
export class <%= pascalCase(name) %>Service {
 
  // camelCase → productOrderService
  private readonly <%= camelCase(name) %>Repository;
 
  // constantCase → PRODUCT_ORDER_TABLE
  private readonly TABLE = '<%= constantCase(name) %>_TABLE';
 
  // kebabCase → product-order (for URLs)
  getRoute() {
    return '/api/<%= kebabCase(name) %>';
  }
}

Result:

import { Injectable } from '@nestjs/common';
 
@Injectable()
export class ProductOrderService {
 
  private readonly productOrderRepository;
 
  private readonly TABLE = 'PRODUCT_ORDER_TABLE';
 
  getRoute() {
    return '/api/product-order';
  }
}

Conditions (if / else)#

Use <% if (...) { %> for conditional blocks.

Note: <% %> blocks (without =) do not produce output — they are logic only.

// Template (parameter: useAsync = boolean)
 
export class <%= pascalCase(name) %>Service {
 
<% if (useAsync === 'TRUE') { %>
  async findAll(): Promise<any[]> {
    return [];
  }
<% } else { %>
  findAll(): any[] {
    return [];
  }
<% } %>
 
}

Note: Boolean parameters are converted to the string "TRUE" or "FALSE" inside the template.

Input: name = "Order", useAsync = true

Result:

export class OrderService {
 
  async findAll(): Promise<any[]> {
    return [];
  }
 
}

Conditions Based on Options#

Very useful when you have a parameter of type options (choices):

// Template (parameter: type = "http" | "grpc" | "queue")
 
<% if (type === 'http') { %>
import { Controller, Get } from '@nestjs/common';
 
@Controller('<%= kebabCase(name) %>')
export class <%= pascalCase(name) %>Controller {}
<% } else if (type === 'grpc') { %>
import { GrpcMethod } from '@nestjs/microservices';
 
export class <%= pascalCase(name) %>GrpcController {}
<% } else { %>
import { MessagePattern } from '@nestjs/microservices';
 
export class <%= pascalCase(name) %>Consumer {}
<% } %>

Loops (for)#

// Template (parameter: fields = "id,name,email" — comma separated)
 
export interface <%= pascalCase(name) %> {
<% fields.split(',').forEach(field => { %>
  <%= field.trim() %>: string;
<% }) %>
}

Input: name = "User", fields = "id, name, email"

Result:

export interface User {
  id: string;
  name: string;
  email: string;
}

Dynamic Output Path#

You can also use casing transformations in the output path in configs.yaml:

templates:
  - name: service
    output: src/services/<%= pascalCase(name) %>Service.ts
    #                     ↑ filename automatically in PascalCase
name InputOutput path
usersrc/services/UserService.ts
product ordersrc/services/ProductOrderService.ts
auth-tokensrc/services/AuthTokenService.ts

Template Comments#

Use <%# %> for comments — they will not appear in the output:

<%# This is a template comment, it will not be generated %>
export class <%= pascalCase(name) %> {
  <%# TODO: add methods as needed %>
}

Lokio Check Annotations#

Special annotations for lokio check — defines automatic lint rules:

<%# @lokio required danger "Must have constructor" constructor %>
<%# @lokio forbidden warning "Do not use console.log" console\.log %>
 
export class <%= pascalCase(name) %>Service {
  constructor() {}
}

See Advanced Features → for the full explanation of lokio check.


Tips & Tricks#

Avoiding Extra Whitespace#

EJS can produce blank lines from <% %> tags. Use - to strip whitespace:

<%- /* strip output */ %>

Or arrange the template so condition blocks don't add blank lines:

export class Example {
<% if (withMethod) { %>  doSomething() {}
<% } %>}

Nested Transformations#

// Combining plural + pascalCase
<%= pascalCase(plural(name)) %>
// Input: "product" → "Products"
// Input: "user order" → "UserOrders"

Using JS Variables in Templates#

<%
  const className = pascalCase(name) + 'Service';
  const routeName = kebabCase(name);
%>
export class <%= className %> {
  route = '<%= routeName %>';
}