I am learning how to use the Adonis framework (v5) and as the tradition dictates it, I am creating a todo list api to test it out.
The problem I am having is regarding the relationship between the User and Todo entities.
Here are the two models:
// file: app/Models/Todo.ts
export default class Todo extends BaseModel {
#column({ isPrimary: true })
public id: number
#belongsTo(() => User, {
foreignKey: 'id',
})
public author: BelongsTo<typeof User>
#column()
public completed: boolean
#column()
public title: string
#column()
public description: string | null
#column.dateTime({ autoCreate: true })
public createdAt: DateTime
#column.dateTime({ autoCreate: true, autoUpdate: true })
public updatedAt: DateTime
}
// file: app/Models/User.ts
export default class User extends BaseModel {
#column({ isPrimary: true })
public id: number
#column()
public username: string
#column({ serializeAs: null })
public password: string
#hasMany(() => Todo, {
foreignKey: 'author',
})
public todos: HasMany<typeof Todo>
#column.dateTime({ autoCreate: true })
public createdAt: DateTime
#column.dateTime({ autoCreate: true, autoUpdate: true })
public updatedAt: DateTime
#beforeSave()
public static async hashPassword(user: User) {
if (user.$dirty.password) {
// Only hash password if required
user.password = await Hash.make(user.password)
}
}
}
I did not include the migrations files but I will edit this question if they are needed.
What I am expecting is that I should be able to create and save Users into my database, the same for Todo entries and link the todo entries to their author. The documentation provides us with an example but I don't see where I am doing something wrong.
So to test it out I am using the node ace repl command as follow:
// log of running the commands in the AdonisJS v5 REPL
> loadModels()
recursively reading models from "app/Models"
Loaded models module. You can access it using the "models" variable
> undefined
> const testUser = await models.User.create({ username: 'testUser', password: 'password' })
undefined
> await testUser.related('todos').create({ title: 'Example todo entry' })
Uncaught:
Exception: E_MISSING_MODEL_ATTRIBUTE: "User.todos" expects "author" to exist on "Todo" model, but is missing
at <my-app-directory>\REPL23:1:39
at Proxy.related (<my-app-directory>\node_modules\#adonisjs\lucid\build\src\Orm\BaseModel\index.js:1436:18)
at HasMany.boot (<my-app-directory>\node_modules\#adonisjs\lucid\build\src\Orm\Relations\HasMany\index.js:74:12)
at KeysExtractor.extract (<my-app-directory>\node_modules\#adonisjs\lucid\build\src\Orm\Relations\KeysExtractor.js:28:39)
at Array.reduce (<anonymous>)
at <my-app-directory>\node_modules\#adonisjs\lucid\build\src\Orm\Relations\KeysExtractor.js:32:23
>
I don't understand the error message since author actually exists on the Todo model.
How can I solve this problem and get my todo app up and running?
Thank you by advance!
The error is that you are missing a field inside your model.
You need to define all fields with the #column() decorator. In your example, you are not defining the column author.
When creating a relation, you must have one column (the FK) and one relation.
If we assume that you have a column user_id inside your todos table, then you need to add the column user_id inside your Todo model.
Here is a correct example:
class User extends BaseModel {
// ...
#hasMany(() => Todo)
todos: HasMany<typeof Todo>
}
class Todo extends BaseModel {
#column()
user_id: number
#belongsTo(() => User)
author: BelongsTo<typeof User>
}
Related
I am trying to get a user instance based on id (same happens for other attributes such as email. Inside the Service, this is my code:
#Injectable()
export class UserService {
#InjectRepository(User)
private readonly repository: Repository<User>;
async findOne(id: number): Promise<User> {
const user = await this.repository.findOne(id);
return user;
}
}
and my User entity is:
#Entity()
export class User {
#PrimaryGeneratedColumn()
public id: number;
#Column({ type: 'varchar', length: 120 })
public name: string;
#Column({ type: 'varchar', length: 120 })
public email: string;
}
The problem is that I always get this error:
src/api/user/user.service.ts - error TS2559: Type 'number' has no properties in common with type 'FindOneOptions<User>'.
Other methods such as getAll work just fine:
public getAllUsers(): Promise<User[]> {
return this.repository.find();
}
There are some breaking changes in typeorm. I wouldn't suggest downgrading, instead check the latest methods.
findOne(id);
is now changed to
findOneBy({
id: id // where id is your column name
})
And find() is now
find({
select: {
id: true,
email: true,
password: true,
},
});
Please check this link for more information.
are you using the latest version of typeorm? Then downgrade it to typeorm#0.2 because #nestjs/typeorm#8.0 might not support the latest one yet. You can read the changes of typeorm#0.3 here: https://github.com/typeorm/typeorm/releases/tag/0.3.0
Actually you don't need to downgrade the typeorm package. Just changed to findOne by this:
async findOne(id: number): Promise<User> {
const user = await this.repository.findOne({
where: { id }
});
return user;
}
check-in your package.json file and replace your version of typeorm with this one "typeorm": "^0.2.34"
the problem is the typeorm version , try typeorm version 0.2.25 and it will be works
Also can someone explain this. I am looking at a code where
async findOne(id: FindOneOptions<User>): Promise<User> {
const user = await this.repository.findOne(id)
return user;
}
this is done. Although it does not work but why did we declare the number id as FindOneOptions<User>
findOne(id) signature was dropped. Use following syntax instead:
const user = await userRepository.findOneBy({
id: id // where id is your column name
})
According to the latest version of Typeorm, findOne expression has been changed as above.
I have an entity
#Column()
name: string;
#IsEmail()
email: string;
#Column({ select: false })
autogeneratedCode: string;
I'm getting name and string only in my GET request response which is expected.
But when I'm hit my POST Api with body, it is returning name, email, autogeneratedCode as well.
I need to hide autogeneratedCode in all CRUD responses.
Is there anyway to do that? or Am I missing something here?
You can use #Exclude() from 'class-transformer'
Example
import { Exclude } from 'class-transformer';
#Entity()
export class User {
#Column()
name: string;
#IsEmail()
email: string;
#Column({ select: false })
#Exclude()
autogeneratedCode: string;
constructor(entity: Partial<User>) {
Object.assign(this, entity);
}
}
Then you can use the constructor to create a new object excluding the #Exclude() properties.
export class UserService {
constructor(
#InjectRepository(User)
private userRepository: Repository<User>
) {}
public async createUser(user: User): Promise<User> {
return new User(
await this.userRepository.save(user)
);
}
}
NestJS Doc on Serialization
https://docs.nestjs.com/techniques/serialization#exclude-properties
I'm trying to insert a record where one of the fields is of type JSONB
Below is the format I'm trying to build and at the end the error that is being issued.
The field in question is veiculo
orm is trying to access the inside of the json to try to indetify the field.
It should insert the complete object.
I thank the attention. Thanks.
Post
{
veiculo: {
placa: 'PLACA',
motorista: 'NOME',
contato: 'CONTATO',
casa: true,
horaEntrada: '2022-01-20T05:02:22.000Z'
},
fornecedor: 'ALIMENTOS',
lab: 'sim',
}
Model
import { DateTime } from 'luxon'
import { BaseModel, column } from '#ioc:Adonis/Lucid/Orm'
export default class Produto extends BaseModel {
#column({ isPrimary: true })
public id: number
#column()
public fornecedor: string
#column()
public lab: string
#column()
public veiculo: Object
#column.dateTime({ autoCreate: true })
public createdAt: DateTime
#column.dateTime({ autoCreate: true, autoUpdate: true })
public updatedAt: DateTime
Migration
public async up () {
this.schema.createTable(this.tableName, (table) => {
table.increments('id')
table.string('fornecedor'
table.string('lab')
table.jsonb('veiculo')
/**
* Uses timestamptz for PostgreSQL and DATETIME2 for MSSQL
*/
table.timestamp('created_at', { useTz: true })
table.timestamp('updated_at', { useTz: true })
})
}
Error
Error: ER_BAD_FIELD_ERROR: Unknown column 'placa' in 'field list'
Resolved with await dataNfe.related('items').createMany(items)
Let's say we are constructing our very first entities for a new web application.
There is an entity for users:
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column()
username: string;
#Column()
password: string;
#OneToOne(() => Author)
author: Author;
Users can be Authors after registration. So there's a one-to-one relationship between users and authors table. Let's say we have another table called books, an author can have multiple books.
export class Author {
#PrimaryGeneratedColumn()
id: number;
#Column()
Name: string;
#OneToMany(() => Book, (Book) => Book.Author)
Book: Book[];
#OneToOne(() => User)
#JoinColumn()
User: User;
Here is a sample entity for Books repository:
export class Book {
#PrimaryGeneratedColumn()
id: number;
#Column()
Name: string;
#Column()
ISIN: string;
#OneToOne(() => Author)
#JoinColumn()
Author: Author;
The question is, when we migrated these entities and built our very first/clean database, how can we insert the data via calling API?
A sample service for API Post method:
#Injectable()
export class BookService {
constructor(
#InjectRepository(Book)
private BookRepository: Repository<Book>,
) {}
async create(createBookDto: CreateBookDto) {
await this.BookRepository.insert(createBookDto);
}
}
And the Controller:
#Controller('books')
export class BookController {
constructor(private readonly BookService: BookService) {}
#Post('/Book')
create(#Body() createBookDto: CreateBookDto) {
return this.BookService.create(createBookDto);
}
}
The problem here is that when I want to create a new book by POSTing data to the API route, it needs the Author to be defined. So how can I post existing user-books-author data into the database via this service?
The best option I think of is to create a new instance of the classes, get the data from request #Body and assign it to the objects of the class then save it to the database.
But I think it's not a good solution, as it's very preferred to use repositories instead of object-class type.
I think you do a mistake in your entity definition. There is OneToMany relation between Author to Book.
Modify your book entity like this
export class Book {
#PrimaryGeneratedColumn()
id: number;
#Column()
Name: string;
#Column()
ISIN: string;
#ManyToOne(() => Author, author=> author.book, { nullable: true })
#JoinColumn()
Author: Author;
{ nullable: true } this will allow you to save your book data without having an author.
Modify your service to save the book information
public async create(createBookDTO: CreateBookDTO) {
try {
if (
createBookDTO.hasOwnProperty('author') &&
createTaskDTO.author
) {
const author = await this.connection.manager.findOne(Author, {
where: {
id: createBookDTO.author,
},
});
if (author) {
createBookDTO.author = author;
} else {
throw new NotAcceptableException('Author not found');
}
}
return await this.bookRepo.save(createBookDTO);
} catch (err) {
throw new HttpException(err, err.status || HttpStatus.BAD_REQUEST);
}
}
I am a beginner in NestJS and I want to write a DTO for below structure -
{
something: {
info: {
title: string,
score: number,
description: string,
time: string,
DateOfCreation: string
},
Store: {
item: {
question: string,
options: {
item: {
answer: string,
description: string,
id: string,
key: string,
option: string
}
}
}
}
}
}
I want to write a DTO for that nested Data object. I can't find a solid example for writing nested DTO in NestJS. I am a beginner in NestJS and I have never worked with DTO before. So please don't assume that I know something. I am using it with Mongoose.
You will have to create separate classes for each object in your schema and a main class which will import all the classes.
class Info {
readonly title:string
readonly score:number
readonly description:string
readonly dateOfCreation:Date
}
export class SampleDto {
#Type(() => Info)
#ValidateNested()
readonly info: Info
...Follow same for the rest of the schema
}
Refer: https://github.com/typestack/class-validator#validating-nested-objects
//example :
export class UserBaseDto {
#ApiProperty({
type: String,
required: true,
description: 'email user, minlength(4), maxlength(40)',
default: "test#email.com",
})
#IsString()
#MinLength(4)
#MaxLength(40)
#IsNotEmpty()
email: string;
//....enter code here
}