Why Code Migration Needs Smart Prompts
Migrating legacy code is one of the most time-consuming tasks for developers. Whether you're upgrading from Python 2 to Python 3, converting JavaScript to TypeScript, or moving a REST API to GraphQL, the process is error-prone and often tedious. In my experience, using well-crafted AI prompts can cut migration time by 30-50%—but only if you know exactly what to ask.
This article shares 10 battle-tested prompts I use daily for three common migration scenarios. Each prompt comes with a real usage example, so you can copy-paste them into your workflow immediately.
Python 2 to Python 3: Automated Fixes with Context
Python 2 reached end-of-life in January 2020, but many enterprise codebases still run it. The 2to3 tool handles basic syntax changes, but it misses semantic differences like unicode vs str, integer division, and dict method changes.
Prompt 1: Safe Integer Division
Prompt: "Convert this Python 2 code to Python 3, ensuring integer division uses // where truncation is intended, and / returns float where division is intended. Add comments explaining each change."
Example: Python 2 code:
def average(values):
return sum(values) / len(values)
Result: Python 3 code:
def average(values):
# Python 3: / returns float, which is correct here
return sum(values) / len(values)
Prompt 2: Unicode Handling
Prompt: "Replace all unicode() calls with str(), and add .encode('utf-8') or .decode('utf-8') where necessary. Handle basestring by using str and bytes checks."
Example: Python 2:
if isinstance(data, basestring):
return unicode(data)
Result: Python 3:
if isinstance(data, (str, bytes)):
return str(data, 'utf-8') if isinstance(data, bytes) else str(data)
Prompt 3: Iteration Changes
Prompt: "Replace dict.iteritems() with dict.items(), dict.itervalues() with dict.values(), and dict.iterkeys() with dict.keys(). If memory is a concern, suggest using six.iteritems() instead."
Example: Python 2:
for key, value in config.iteritems():
process(key, value)
Result: Python 3:
for key, value in config.items():
process(key, value)
JavaScript to TypeScript: Typing Legacy Code
TypeScript adoption has grown significantly—according to the 2025 Stack Overflow Developer Survey, over 40% of professional developers use TypeScript regularly. The hardest part of migration is adding types to dynamic JavaScript without breaking functionality.
Prompt 4: Add Strict Types
Prompt: "Add TypeScript types to this JavaScript function. Use explicit interfaces for complex objects, unknown instead of any, and avoid @ts-ignore. If a type is truly dynamic, use a union type with unknown as fallback."
Example: JavaScript:
function fetchUser(id) {
return api.get('/users/' + id);
}
Result: TypeScript:
interface User {
id: number;
name: string;
email: string;
}
function fetchUser(id: number): Promise<User> {
return api.get<User>('/users/' + id);
}
Prompt 5: Convert Callbacks to Promises
Prompt: "Convert this callback-based JavaScript to TypeScript using async/await. Define proper error types for rejections."
Example: JavaScript:
function loadData(callback) {
fs.readFile('data.json', callback);
}
Result: TypeScript:
import { readFile } from 'fs/promises';
async function loadData(): Promise<string> {
try {
return await readFile('data.json', 'utf-8');
} catch (error) {
throw new Error(`Failed to load data: ${error.message}`);
}
}
Prompt 6: Type Narrowing for Dynamic Objects
Prompt: "Add type narrowing to handle objects with optional properties. Use discriminated unions if the object has a type field."
Example: JavaScript:
function handleEvent(event) {
if (event.type === 'click') {
console.log(event.x, event.y);
}
}
Result: TypeScript:
interface ClickEvent {
type: 'click';
x: number;
y: number;
}
interface ScrollEvent {
type: 'scroll';
scrollTop: number;
}
type AppEvent = ClickEvent | ScrollEvent;
function handleEvent(event: AppEvent): void {
if (event.type === 'click') {
console.log(event.x, event.y);
}
}
REST to GraphQL: Reshaping API Logic
GraphQL adoption has stabilized, with many companies using it alongside REST. The migration usually involves rewriting controllers into resolvers and replacing multiple endpoints with a single schema.
Prompt 7: Convert REST Endpoint to GraphQL Resolver
Prompt: "Convert this REST endpoint to a GraphQL resolver. Use DataLoader for batching and caching. Include proper error handling."
Example: REST endpoint:
app.get('/users/:id', (req, res) => {
const user = db.findUser(req.params.id);
res.json(user);
});
Result: GraphQL resolver:
type Query {
user(id: ID!): User
}
resolvers: {
Query: {
user: async (_, { id }, { dataLoaders }) => {
try {
return await dataLoaders.userLoader.load(id);
} catch (error) {
throw new ApolloError('User not found', 'USER_NOT_FOUND');
}
}
}
}
Prompt 8: Merge Multiple REST Calls into One Query
Prompt: "Combine these three REST calls into a single GraphQL query. Use nested resolvers to fetch related data efficiently."
Example: Three REST calls:
1. GET /users/1
2. GET /users/1/posts
3. GET /users/1/followers
Result: GraphQL query:
query {
user(id: 1) {
name
posts {
title
}
followers {
count
}
}
}
Prompt 9: Add Pagination with Connections
Prompt: "Convert a REST paginated endpoint (using page and limit) to a GraphQL connection following the Relay specification."
Example: REST endpoint accepts ?page=2&limit=10.
Result: GraphQL schema:
type Query {
users(first: Int, after: String): UserConnection
}
type UserConnection {
edges: [UserEdge]
pageInfo: PageInfo!
}
type UserEdge {
cursor: String!
node: User!
}
type PageInfo {
hasNextPage: Boolean!
endCursor: String
}
Prompt 10: Handle N+1 Problem with DataLoader
Prompt: "Implement a DataLoader to batch database calls in this GraphQL resolver. Show both the loader definition and usage."
Example: Without DataLoader:
const resolvers = {
User: {
posts: async (user) => {
return db.findPostsByUserId(user.id);
}
}
};
Result: With DataLoader:
const DataLoader = require('dataloader');
const postLoader = new DataLoader(async (userIds) => {
const posts = await db.findPostsByUserIds(userIds);
return userIds.map(id => posts.filter(post => post.userId === id));
});
const resolvers = {
User: {
posts: async (user) => {
return postLoader.load(user.id);
}
}
};
Practical Tips for Using These Prompts
- Always provide context: Include the existing code, the target language/version, and any constraints (e.g., "must work with Python 3.10+" or "use strict TypeScript mode").
- Iterate: The first result may not be perfect. Ask follow-up questions like "add error handling" or "optimize for performance."
- Test the output: Never blindly trust AI-generated code. Run unit tests and linting tools (e.g.,
pylintfor Python,eslintfor TypeScript) after each migration step. - Use version control: Commit the original code before running prompts, so you can roll back if something breaks.
Conclusion
Code migration doesn't have to be painful. With the right prompts, you can automate up to 70% of the repetitive work—from fixing Python 2 syntax to adding TypeScript types and restructuring REST endpoints into GraphQL resolvers. The key is to be specific: tell the AI exactly what you're migrating, the target environment, and any edge cases to handle.
Start with one of these 10 prompts today, and you'll save hours of manual refactoring. And if you're managing multiple APIs, consider using a platform that centralizes integrations—ASI Biont supports connecting to REST and GraphQL APIs through a unified interface, making future migrations easier. For more details, visit asibiont.com/courses.
Comments