Skip to content

Migration Guide: Moving to USL

Overview

This guide helps teams migrate existing applications to USL from popular frameworks. We cover common migration patterns, code examples, and best practices for a smooth transition.

Target Frameworks: - Django (Python) - Ruby on Rails - Express.js (Node.js) - Spring Boot (Java)

Migration Timeline: 4-12 weeks depending on application size


Table of Contents

  1. Migration Strategy
  2. Django to USL
  3. Rails to USL
  4. Express to USL
  5. Spring Boot to USL
  6. Common Patterns
  7. Testing Strategy
  8. Deployment

Migration Strategy

Phase 1: Parallel Run (Weeks 1-4) - Keep existing application running - Build new features in USL - Proxy new endpoints to USL service - Gradual traffic shifting

Phase 2: Core Migration (Weeks 5-8) - Migrate database schema - Rewrite core business logic - API compatibility layer - Comprehensive testing

Phase 3: Complete Cutover (Weeks 9-12) - Migrate remaining features - Data migration - Sunset old application - Monitor and optimize

Key Principles

  1. Incremental Migration: Don't rewrite everything at once
  2. Feature Parity First: Match existing functionality before adding new features
  3. Test Coverage: Maintain or improve test coverage during migration
  4. Monitoring: Enhanced monitoring during transition
  5. Rollback Plan: Always have a rollback strategy

Django to USL

Overview

Django is a Python web framework with ORM, admin interface, and batteries-included philosophy. USL provides similar capabilities with type safety across the full stack.

Feature Mapping

Django Feature USL Equivalent Notes
Models (ORM) Entity definitions Similar syntax, stronger types
Views API handlers More concise, type-safe
URLs API routing Automatic from entity definitions
Forms Input validation Built into API definitions
Admin Auto-generated admin UI Coming in v1.1
Middleware Request/response hooks Similar concept
Migrations USL migrations Automatic from schema changes
Auth Built-in auth system More secure by default

Code Examples

Django Model → USL Entity

Django:

# models.py
from django.db import models
from django.contrib.auth.models import User

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    published = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['-created_at']

    def __str__(self):
        return self.title

USL:

entity Post {
  id: uuid primary default(uuid_v4())
  title: string validate(length >= 1, length <= 200)
  content: text
  author_id: uuid foreign(User)
  author: User relation(author_id)
  published: boolean default(false)
  created_at: timestamp default(now())
  updated_at: timestamp default(now()) on_update(now())
}

// Automatic ordering by created_at in queries

Django View → USL API

Django:

# views.py
from django.http import JsonResponse
from django.views.decorators.http import require_http_methods
from .models import Post

@require_http_methods(["GET"])
def list_posts(request):
    published_only = request.GET.get('published', 'true') == 'true'

    posts = Post.objects.filter(published=published_only)

    return JsonResponse({
        'posts': [
            {
                'id': post.id,
                'title': post.title,
                'author': post.author.username,
                'created_at': post.created_at.isoformat()
            }
            for post in posts
        ]
    })

USL:

api PostAPI {
  GET "/posts" list_posts {
    auth: required

    query {
      published: boolean default(true)
    }

    handler {
      posts = Post.where(published == query.published)
        .order_by(created_at, desc)
        .include([author])
        .all()

      return {
        status: 200,
        data: posts
      }
    }
  }
}

Django Forms → USL Validation

Django:

# forms.py
from django import forms

class PostForm(forms.Form):
    title = forms.CharField(max_length=200, required=True)
    content = forms.CharField(widget=forms.Textarea, required=True)
    published = forms.BooleanField(required=False)

    def clean_title(self):
        title = self.cleaned_data['title']
        if len(title) < 5:
            raise forms.ValidationError("Title must be at least 5 characters")
        return title

USL:

api PostAPI {
  POST "/posts" create_post {
    auth: required

    input {
      title: string validate(length >= 5, length <= 200)
      content: string validate(length >= 10)
      published: boolean default(false)
    }

    handler {
      post = Post.create({
        ...input,
        author_id: current_user.id
      })

      return {
        status: 201,
        data: post
      }
    }
  }
}

Django Authentication

Django:

# views.py
from django.contrib.auth import authenticate, login
from django.contrib.auth.decorators import login_required

@login_required
def protected_view(request):
    return JsonResponse({'user': request.user.username})

USL:

api UserAPI {
  POST "/auth/login" login {
    input {
      email: string validate(email_format)
      password: string
    }

    handler {
      user = User.where(email == input.email).first()

      if !user or !verify_password(input.password, user.password_hash) {
        return error(401, "Invalid credentials")
      }

      token = generate_jwt(user.id)

      return {
        status: 200,
        data: {
          token: token,
          user: user
        }
      }
    }
  }

  GET "/protected" protected {
    auth: required

    handler {
      return {
        status: 200,
        data: {
          user: current_user.email
        }
      }
    }
  }
}

Migration Steps

  1. Set Up USL Project

    usl init --name my-django-app --template django-migration
    

  2. Convert Models to Entities

  3. Start with core models
  4. Map Django field types to USL types
  5. Define relationships
  6. Add validation rules

  7. Migrate Database Schema

    # Export Django schema
    python manage.py dumpdata --format=json > data.json
    
    # Generate USL migrations
    usl migrate:generate
    
    # Run migrations
    usl migrate
    

  8. Convert Views to API Handlers

  9. One view at a time
  10. Maintain API contract
  11. Add type safety
  12. Improve error handling

  13. Data Migration

    # Use USL data import
    usl import:django --data data.json
    

  14. Testing

  15. Write USL tests
  16. Integration tests for API compatibility
  17. Load testing
  18. Security testing

  19. Deploy

    usl deploy staging
    # Test thoroughly
    usl deploy production
    


Rails to USL

Feature Mapping

Rails Feature USL Equivalent Notes
ActiveRecord Entity definitions Similar conventions
Controllers API handlers RESTful by default
Routes Automatic routing Convention over configuration
Validations Built-in validators Type-safe
Migrations Auto migrations From schema
ActionMailer Email service Built-in
ActiveJob Background jobs Built-in
Asset Pipeline Frontend generation TypeScript client

Code Examples

Rails Model → USL Entity

Rails:

# app/models/user.rb
class User < ApplicationRecord
  has_many :posts
  has_secure_password

  validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
  validates :name, presence: true, length: { minimum: 2, maximum: 100 }

  scope :active, -> { where(active: true) }

  def full_name
    "#{first_name} #{last_name}"
  end
end

USL:

entity User {
  id: uuid primary default(uuid_v4())
  email: string unique validate(email_format) indexed
  password_hash: encrypted<string>
  first_name: string validate(length >= 2, length <= 100)
  last_name: string validate(length >= 2, length <= 100)
  active: boolean default(true) indexed
  created_at: timestamp default(now())
  updated_at: timestamp default(now()) on_update(now())

  posts: [Post] relation(user_id)

  // Computed field
  full_name: string computed(first_name + " " + last_name)
}

// Scope equivalent
query active_users {
  User.where(active == true)
}

Rails Controller → USL API

Rails:

# app/controllers/posts_controller.rb
class PostsController < ApplicationController
  before_action :authenticate_user!
  before_action :set_post, only: [:show, :update, :destroy]

  def index
    @posts = Post.where(published: true).order(created_at: :desc)
    render json: @posts, include: [:author]
  end

  def create
    @post = current_user.posts.build(post_params)

    if @post.save
      render json: @post, status: :created
    else
      render json: @post.errors, status: :unprocessable_entity
    end
  end

  private

  def set_post
    @post = Post.find(params[:id])
  end

  def post_params
    params.require(:post).permit(:title, :content, :published)
  end
end

USL:

api PostAPI {
  GET "/posts" index {
    auth: required

    handler {
      posts = Post.where(published == true)
        .order_by(created_at, desc)
        .include([author])
        .all()

      return {
        status: 200,
        data: posts
      }
    }
  }

  POST "/posts" create {
    auth: required

    input {
      title: string validate(length >= 1, length <= 200)
      content: text
      published: boolean default(false)
    }

    handler {
      post = Post.create({
        ...input,
        user_id: current_user.id
      })

      return {
        status: 201,
        data: post
      }
    }
  }

  GET "/posts/:id" show {
    auth: required

    handler {
      post = Post.find(params.id)

      if !post {
        return error(404, "Post not found")
      }

      return {
        status: 200,
        data: post
      }
    }
  }
}

Migration Steps

  1. Install USL

    gem install usl-rails-converter  # Helper gem
    usl init --from rails
    

  2. Convert Models

    usl convert:models app/models/**/*.rb
    

  3. Convert Controllers

    usl convert:controllers app/controllers/**/*.rb
    

  4. Migrate Data

    # Export Rails data
    rake db:data:dump
    
    # Import to USL
    usl import:rails --data db/data.yml
    

  5. Test & Deploy


Express to USL

Feature Mapping

Express Feature USL Equivalent
Routes API handlers
Middleware Request/response hooks
mongoose/TypeORM Entity definitions
Joi/Yup validation Built-in validation
Passport.js Built-in auth
Express-validator Type-safe validation

Code Examples

Express Route → USL API

Express:

// routes/posts.js
const express = require('express');
const router = express.Router();
const Post = require('../models/Post');
const auth = require('../middleware/auth');

router.get('/posts', auth, async (req, res) => {
  try {
    const posts = await Post.find({ published: true })
      .populate('author')
      .sort({ createdAt: -1 });

    res.json({ posts });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

router.post('/posts', auth, async (req, res) => {
  try {
    const { title, content } = req.body;

    if (!title || title.length < 5) {
      return res.status(400).json({ error: 'Title must be at least 5 characters' });
    }

    const post = new Post({
      title,
      content,
      author: req.user.id
    });

    await post.save();

    res.status(201).json({ post });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

module.exports = router;

USL:

api PostAPI {
  GET "/posts" list {
    auth: required

    handler {
      posts = Post.where(published == true)
        .order_by(created_at, desc)
        .include([author])
        .all()

      return {
        status: 200,
        data: { posts }
      }
    }
  }

  POST "/posts" create {
    auth: required

    input {
      title: string validate(length >= 5)
      content: text
    }

    handler {
      post = Post.create({
        ...input,
        author_id: current_user.id
      })

      return {
        status: 201,
        data: { post }
      }
    }
  }
}

Migration Steps

  1. Create USL Project

    usl init --from express
    

  2. Convert Models

  3. Mongoose schemas → USL entities
  4. Add type safety
  5. Improve validation

  6. Convert Routes

  7. Express routes → USL API handlers
  8. Remove boilerplate
  9. Add automatic validation

  10. Migrate Middleware

  11. Auth middleware → USL auth system
  12. Custom middleware → USL hooks

  13. Test & Deploy


Spring Boot to USL

Feature Mapping

Spring Boot Feature USL Equivalent
JPA Entities Entity definitions
RestControllers API handlers
Spring Security Built-in security
Bean Validation Type-safe validation
Spring Data Query system
@Transactional Built-in transactions

Code Examples

Spring Entity → USL Entity

Spring Boot:

@Entity
@Table(name = "posts")
public class Post {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;

    @Column(nullable = false, length = 200)
    private String title;

    @Column(columnDefinition = "TEXT")
    private String content;

    @ManyToOne
    @JoinColumn(name = "author_id")
    private User author;

    private Boolean published = false;

    @CreatedDate
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;
}

USL:

entity Post {
  id: uuid primary default(uuid_v4())
  title: string validate(length >= 1, length <= 200)
  content: text
  author_id: uuid foreign(User)
  author: User relation(author_id)
  published: boolean default(false)
  created_at: timestamp default(now())
  updated_at: timestamp default(now()) on_update(now())
}


Common Patterns

Authentication

Pattern: JWT-based authentication

Before (Generic):

1. User submits credentials
2. Server validates against database
3. Generate JWT token
4. Return token to client
5. Client sends token in Authorization header
6. Server validates token on each request

USL:

api AuthAPI {
  POST "/auth/login" login {
    input {
      email: string validate(email_format)
      password: string
    }

    handler {
      user = User.where(email == input.email).first()

      if !user or !verify_password(input.password, user.password_hash) {
        return error(401, "Invalid credentials")
      }

      token = generate_jwt(user.id, expires_in: 24 hours)

      return {
        status: 200,
        data: { token, user }
      }
    }
  }
}

// All endpoints with auth: required automatically validate JWT

Pagination

USL Pattern:

api PostAPI {
  GET "/posts" list {
    query {
      page: int default(1) validate(>= 1)
      per_page: int default(20) validate(>= 1, <= 100)
    }

    handler {
      offset = (query.page - 1) * query.per_page

      posts = Post.query()
        .limit(query.per_page)
        .offset(offset)
        .all()

      total = Post.count()

      return {
        status: 200,
        data: posts,
        meta: {
          page: query.page,
          per_page: query.per_page,
          total: total,
          total_pages: ceil(total / query.per_page)
        }
      }
    }
  }
}

File Uploads

USL Pattern:

api FileAPI {
  POST "/upload" upload_file {
    auth: required

    input {
      file: file validate(
        max_size: 10MB,
        allowed_types: [image/jpeg, image/png, application/pdf]
      )
      title: string? optional
    }

    handler {
      // Automatic virus scanning
      if !scan_file(input.file) {
        return error(400, "File failed security scan")
      }

      // Upload to storage (S3, etc.)
      url = storage.upload(input.file, {
        path: "uploads/${current_user.id}/${input.file.name}",
        encryption: true
      })

      // Save metadata
      file = File.create({
        user_id: current_user.id,
        title: input.title || input.file.name,
        url: url,
        size: input.file.size,
        mime_type: input.file.type
      })

      return {
        status: 201,
        data: file
      }
    }
  }
}


Testing Strategy

Unit Tests

USL Test Syntax:

test "Create post with valid data" {
  given {
    user = create_test_user()
    post_data = {
      title: "Test Post",
      content: "This is a test",
      published: false
    }
  }

  when {
    response = api.post("/posts", post_data, auth: user)
  }

  then {
    assert response.status == 201
    assert response.data.title == "Test Post"
    assert response.data.author_id == user.id
  }
}

Migration Testing

  1. Parallel Run Testing
  2. Run both old and new systems
  3. Compare responses
  4. Identify discrepancies

  5. Data Integrity Testing

  6. Verify all data migrated
  7. Check relationships preserved
  8. Validate no data loss

  9. Performance Testing

  10. Load testing
  11. Compare response times
  12. Identify bottlenecks

Deployment

Zero-Downtime Migration

  1. Phase 1: Deploy USL alongside existing app
  2. Phase 2: Route new traffic to USL
  3. Phase 3: Gradually shift existing traffic
  4. Phase 4: Monitor and optimize
  5. Phase 5: Sunset old application

Rollback Strategy

Always maintain ability to rollback:

# Rollback plan
1. Keep old application running
2. Database compatible with both versions
3. Feature flags for gradual rollout
4. Monitoring for issues
5. One-command rollback procedure


Support

Need help with migration? - Discord: #migration-help - Email: migration-support@usl.dev - Docs: https://usl.dev/migration - Office Hours: Tuesday 2-3 PM ET


Last Updated: January 15, 2026