You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeORM中如何通过ManyToOne关联返回用户名而非用户ID?

需求实现:查询Post时返回关联User的username字段

我拥有User和Post两个实体,Post实体通过ManyToOne关联User实体。创建Post时传入用户ID可正常关联,但在查询Post的控制器中,返回结果未包含用户相关信息,现需修改查询逻辑,使返回结果中包含User实体的username字段。

现有代码

Post实体

import { Column, CreateDateColumn, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
import { User } from "./User";

@Entity('posts')
export class Post {
    @PrimaryGeneratedColumn('uuid')
    id: string;

    @ManyToOne(() => User)
    @JoinColumn()
    user: User

    @Column()
    title: string;

    @Column()
    desc: string;

    @CreateDateColumn({ 
        type: 'timestamp', 
        precision: 3
      })
      createdAt: Date;    
}

User实体

import { BeforeInsert, BeforeUpdate, Column, Entity, PrimaryGeneratedColumn } from "typeorm";
import bcrypt from 'bcryptjs';

@Entity('users')
export class User {
    @PrimaryGeneratedColumn('uuid')
    id: string;

    @Column()
    username: string;

    @Column()
    email: string;

    @Column()
    password: string;

    @Column({
        default: ''
    })
    tokenResetPass: string;

    @BeforeInsert()
    @BeforeUpdate()
    hashPassword() {
        this.password = bcrypt.hashSync(this.password, 8);
    }
}

查询Post的控制器

import { Request, Response } from "express";
import { AppDataSource } from "../../database/config";
import { Post } from "../../entities/Post";

class FindAllPostsController {
    async index(req: Request, res: Response) {
        try {
            const posts = await AppDataSource.manager.find(Post);
            return res.status(200).json(posts);
        } catch (err) {
            return res.status(500).json(err);
        }
    }
}

export default new FindAllPostsController();

当前返回示例

{
    "id": "27febc4a-1461-4146-a839-bb860a947de2",
    "title": "test 1",
    "desc": "testing a post",
    "createdAt": "2022-12-17T16:52:39.196Z"
}

期望返回示例

{
    "id": "27febc4a-1461-4146-a839-bb860a947de2",
    "user": "username",
    "title": "test 1",
    "desc": "testing a post",
    "createdAt": "2022-12-17T16:52:39.196Z"
}

解决方案

方式一:关联加载后映射结果

在find方法中添加relations选项加载关联的user实体,再将结果映射为期望的格式:

import { Request, Response } from "express";
import { AppDataSource } from "../../database/config";
import { Post } from "../../entities/Post";

class FindAllPostsController {
    async index(req: Request, res: Response) {
        try {
            // 关联加载user实体
            const posts = await AppDataSource.manager.find(Post, {
                relations: ['user']
            });
            
            // 转换为期望的返回格式
            const formattedPosts = posts.map(post => ({
                id: post.id,
                user: post.user.username,
                title: post.title,
                desc: post.desc,
                createdAt: post.createdAt
            }));

            return res.status(200).json(formattedPosts);
        } catch (err) {
            return res.status(500).json(err);
        }
    }
}

export default new FindAllPostsController();

方式二:使用QueryBuilder精准控制查询

通过TypeORM的QueryBuilder可以只查询需要的字段,减少数据传输:

import { Request, Response } from "express";
import { AppDataSource } from "../../database/config";
import { Post } from "../../entities/Post";

class FindAllPostsController {
    async index(req: Request, res: Response) {
        try {
            const posts = await AppDataSource
                .getRepository(Post)
                .createQueryBuilder('post')
                .leftJoinAndSelect('post.user', 'user')
                .select([
                    'post.id',
                    'post.title',
                    'post.desc',
                    'post.createdAt',
                    'user.username'
                ])
                .getMany();
            
            // 映射格式,将user.username转为顶层字段
            const formattedPosts = posts.map(post => ({
                ...post,
                user: post.user.username
            }));

            return res.status(200).json(formattedPosts);
        } catch (err) {
            return res.status(500).json(err);
        }
    }
}

export default new FindAllPostsController();

说明

  • 方式一操作简单,适合需要完整User实体的场景,但会加载User的所有字段;
  • 方式二更高效,仅查询所需字段,适合对性能有要求的场景;
  • 两种方式都能实现期望的返回格式。

内容的提问来源于stack exchange,提问作者Routfin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 11:40:43