A migration order that works
- Scaffold the Python project with austial new. It gives you the root module, a health module and the test setup.
- Port one module at a time, starting with modules that depend on nothing else. Generate each one with austial generate resource, then replace the placeholders.
- Translate DTOs from class-validator classes to Pydantic models. The field types stay the same.
- Keep providers as they are. Constructor injection stays, and the container reads your type hints instead of reflect-metadata.
- Port guards, pipes and interceptors. The decorators and the request order are the same.
- Move the data layer. The built-in ORM is TypeORM-shaped, so entities, repositories and the query builder map across. It supports PostgreSQL and SQLite today.
- Port the tests with Test.create_testing_module, then run old and new side by side behind a proxy and move routes over as each module passes.
NestJS to Austial, task by task
| Task | NestJS (TypeScript) | Austial (Python) |
|---|---|---|
| Create a project | nest new app | austial new app |
| Generate a feature | nest g resource cats | austial g resource cats |
| Route with a parameter | @Get(':id') with @Param('id') | @Get(":id") with id: int in the handler |
| Read the body | @Body() dto: CreateCatDto | dto: CreateCatDto = Body() |
| Validate input | class-validator decorators | Pydantic BaseModel fields |
| Inject a service | constructor(private cats: CatsService) | def __init__(self, cats_service: CatsService) |
| Protect a route | @UseGuards(AuthGuard) | @UseGuards(AuthGuard) |
| Throw an HTTP error | throw new NotFoundException() | raise NotFoundException("...") |
| Configuration | ConfigModule and ConfigService | ConfigModule.for_root() and ConfigService |
| Test a module | Test.createTestingModule() | Test.create_testing_module() |
Porting a controller, service and DTO
A typical NestJS cats module becomes this. The structure is the same: a DTO, an injectable service and a controller that receives the service in its constructor.
from austial import Body, Controller, Get, Injectable, NotFoundException, Post
from pydantic import BaseModel
class CreateCatDto(BaseModel):
name: str
age: int
@Injectable()
class CatsService:
def __init__(self) -> None:
self._cats: dict[int, dict] = {}
def create(self, dto: CreateCatDto) -> dict:
cat = {"id": len(self._cats) + 1, **dto.model_dump()}
self._cats[cat["id"]] = cat
return cat
def find_one(self, id: int) -> dict:
if id not in self._cats:
raise NotFoundException(f"Cat #{id} not found")
return self._cats[id]
@Controller("cats")
class CatsController:
def __init__(self, cats_service: CatsService):
self.cats_service = cats_service
@Post()
async def create(self, dto: CreateCatDto = Body()) -> dict:
return self.cats_service.create(dto)
@Get(":id")
async def find_one(self, id: int) -> dict:
return self.cats_service.find_one(id)What does not carry over
- File names use underscores (cats_controller.py), because Python cannot import a file called cats.controller.py.
- Request data uses marker defaults such as Body(), Query() and Param() instead of parameter decorators.
- Interceptors wrap the handler with a plain async continuation, not an RxJS stream.
- The HTTP platform is FastAPI only. There is no Express or Fastify adapter layer.
- GraphQL is code-first through Strawberry. There is no schema-first mode.
- The built-in ORM supports PostgreSQL and SQLite, and there is no adapter for TypeORM, Sequelize or MikroORM.
Frequently asked questions
Do I have to redesign my architecture to move from NestJS to Python?
No. Austial keeps NestJS's module, controller and provider structure and its request pipeline, so the architecture carries over. What changes is the language, the validation library and the data layer.
Can I migrate a NestJS app gradually?
Yes. Port one module at a time, starting with modules that have no dependencies, and run the old and new services side by side behind a proxy until each module passes its tests.
Will my API documentation still work?
Yes. Austial runs on FastAPI, which generates an OpenAPI schema at /openapi.json and serves interactive docs at /docs.
Last updated