I am developing an API with Nestjs and MongoDB. Well sometimes I have this problem. It doesn't always happen and that's what I have to solve because this error rarely happens.
This is the error:
{
"status": "error",
"message": "\nInvalid this.prisma.people.findMany() invocation in\nC:\ima\empresas\datec\api-official\src\modules\Events\services\utils.service.ts:57: 28\n\n 54 }),\n 55 \n 56 // Get attendees by id\n→ 57 this.prisma.people.findMany(\nError in batch request 3: Error occurred during query execution:\nConnectorError(ConnectorError { user_facing_error: None, kind: RawDatabaseError { code: "unknown", message: "An existing connection was forced to be terminated by the remote host. (os error 10054)" } })"
}
I really can't find much information to solve this error. I have followed the steps in the documentation on Nestjs when using prisma with nestjs. I need help to solve this error, since it is essential to find a solution as soon as possible. Thanks so much for read this question
UPDATE
The Prisma service
// Librarys
import { Injectable } from '#nestjs/common'
import { PrismaClient } from '#prisma/client'
// Interfaces
import { INestApplication, OnModuleInit, OnModuleDestroy } from '#nestjs/common'
#Injectable()
export class PrismaService
extends PrismaClient
implements OnModuleInit, OnModuleDestroy
{
async onModuleInit() {
await this.$connect()
}
async onModuleDestroy() {
await this.$disconnect()
}
async enableShutdownHooks(app: INestApplication) {
this.$on('beforeExit', async () => {
await app.close()
})
}
}
File that starts the application
//Libraries
import { NestFactory } from '#nestjs/core'
import { ValidationPipe } from '#nestjs/common'
import { SwaggerModule, DocumentBuilder } from '#nestjs/swagger'
import { useContainer } from 'class-validator'
// Modules
import { MainModule } from './main.module'
// config
import pk from '#root/package.json'
import { corstOptions } from '#config/cors'
import { PORT, APP_NAME, PUBLIC_URL } from '#config/env'
// Exceptions
import { HttpExceptionFilter } from '#utils/HttpExceptionFilter'
import { PrismaService } from '#root/src/services/prisma'
async function bootstrap() {
// Create application
const app = await NestFactory.create(MainModule)
// Enable cors
app.enableCors(corstOptions)
// Use global 'api' prefix, all calls will come after '/api/*'
app.setGlobalPrefix('api')
// Globally define custom response
app.useGlobalFilters(new HttpExceptionFilter())
// Enable prism on custom validations
useContainer(app.select(MainModule), { fallbackOnErrors: true })
// Get service from primsa and enable 'shutdowns'
const prismaService = app.get(PrismaService)
await prismaService.enableShutdownHooks(app)
// Use 'pipe' validation to validate the 'body' structure
app.useGlobalPipes(
newValidationPipe({
whitelist: true,
transform: true,
forbidUnknownValues: true,
forbidNonWhitelisted: true,
transformOptions: { enableImplicitConversion: true }
})
)
// Create API documentation
const config = new DocumentBuilder()
.addBearerAuth()
.setTitle(`API | ${APP_NAME}`)
.setContact(pk.author.name, pk.author.url, pk.author.email)
.setDescription(pk.description)
.setVersion(pk.version)
.build()
const document = SwaggerModule.createDocument(app, config)
SwaggerModule.setup('/', app, document)
// Listen to the application on the PORT defined in the environment variables
await app.listen(PORT || 0, () => {
// eslint-disable-next-line no-console
console.log(
'\x1b[33m%s\x1b[0m',
`[INFO] The server has been started at '${PUBLIC_URL}'`
)
})
}
bootstrap() // Run application
utils.service of Event module
// Librarys
import { Prisma } from '#prisma/client'
import { Inject, Injectable } from '#nestjs/common'
// Services
import { PrismaService } from '#services/prisma'
// DTO
import { EventBodyDTO } from '#modules/Events/events.dto'
// Arguments
import { EventsArgs } from '#modules/Events/events.args'
#Injectable()
export class UtilsService {
#Inject(PrismaService)
private readonly prisma: PrismaService
private readonly selects = {
'id.title': { id: true, title: true },
'id.fullname': { id: true, fullname: true },
'id.title.attributes': { id: true, title: true, attributes: true },
'id.code.description.attributes': {
id: true,
code: true,
description: true,
attributes: true
}
}
/**
* Get reminders, types of activities, case, client and attendees of an event
* #param {EventBodyDTO} payload Event data
*/
async getEventFields(payload: EventBodyDTO) {
const [activityType, eventCase, client, assistants, reminders] =
await Promise.all([
// Get an activity type by id
this.prisma.parameters.findUnique({
select: this.selects['id.title'],
where: { id: payload.activityType }
}),
// Get a case by id
this.prisma.expedients.findUnique({
where: { id: payload.case },
select: this.selects['id.code.description.attributes']
}),
// Get a person by id
this.prisma.people.findFirst({
select: this.selects['id.fullname'],
where: { isClient: true, id: payload.client }
}),
// Get attendees by id
this.prisma.people.findMany({
select: this.selects['id.fullname'],
where: {
isEmployee: true,
id: { in: payload.assistants }
}
}),
// Get reminders by id
this.prisma.parameters.findMany({
select: this.selects['id.title.attributes'],
where: {
id: { in: payload.reminders }
}
})
])
return {
reminders: reminders,
assistants: assistants,
client: client === null ? {} : client,
case: eventCase === null ? {} : eventCase,
activityType: activityType === null ? {} : activityType
}
/**
* Create filters to filter by customer or event attendees
* #param {EventsArgs} args Arguments to filter
* #returns {Promise<Prisma.EventsFindManyArgs['where']>} Returns an object with filters
*/
async createEventFilters(
args: EventsArgs
): Promise<Prisma.EventsFindManyArgs['where']> {
const filters: Prism.EventsFindManyArgs['where'] = {
userId: args.user
}
// Filter by customer
if (typeof args.client === 'string') {
const clients = await this.prisma.people.findMany({
where: {
isClient: true,
fullname: {
mode: 'insensitive',
contains: args.client
}
},
select: {
id: true,
fullname: true
}
})
filters.OR = []
for (const client of clients) {
filters.OR.push({
client: { equals: client }
})
}
}
// Filter by attendees
if (Array.isArray(args.assistants)) {
const assistants = await this.prisma.people.findMany({
where: {
isEmployee: true,
id: {
in: args.assistants
}
},
select: {
id: true,
fullname: true
}
})
if (!Array.isArray(filters.OR)) {
filters.OR = []
}
filters.OR.push({
assistants: { hasSome: assistants }
})
}
return filters
}
}
Related
error - MissingSchemaError: Schema hasn't been registered for model "post".
Use mongoose.model(name, schema)
at Mongoose.model (/Users/mac/Practice/portfolio_projects/ai-image-generation/node_modules/mongoose/lib/index.js:549:13)
at eval (webpack-internal:///(api)/./src/lib/mongodb/models/post.ts:34:52)
at (api)/./src/lib/mongodb/models/post.ts (/Users/mac/Practice/portfolio_projects/ai-image-generation/.next/server/pages/api/post.js:62:1)
at webpack_require (/Users/mac/Practice/portfolio_projects/ai-image-generation/.next/server/webpack-api-runtime.js:33:42)
at eval (webpack-internal:///(api)/./src/pages/api/post.ts:9:82)
at (api)/./src/pages/api/post.ts (/Users/mac/Practice/portfolio_projects/ai-image-generation/.next/server/pages/api/post.js:82:1)
at webpack_require (/Users/mac/Practice/portfolio_projects/ai-image-generation/.next/server/webpack-api-runtime.js:33:42)
at webpack_exec (/Users/mac/Practice/portfolio_projects/ai-image-generation/.next/server/pages/api/post.js:92:39)
at /Users/mac/Practice/portfolio_projects/ai-image-generation/.next/server/pages/api/post.js:93:28
at Object. (/Users/mac/Practice/portfolio_projects/ai-image-generation/.next/server/pages/api/post.js:96:3)
for db connection
import { MongoClient } from "mongodb";
if (!process.env.MONGODB_URI) {
throw new Error('Invalid/Missing environment variable: "MONGODB_URI"');
}
const uri = process.env.MONGODB_URI;
const options = {};
let client;
let clientPromise: Promise<MongoClient>;
if (process.env.NODE_ENV === "development") {
// In development mode, use a global variable so that the value
// is preserved across module reloads caused by HMR (Hot Module Replacement).
if (!global._mongoClientPromise) {
client = new MongoClient(uri, options);
global._mongoClientPromise = client.connect();
}
clientPromise = global._mongoClientPromise;
} else {
// In production mode, it's best to not use a global variable.
client = new MongoClient(uri, options);
clientPromise = client.connect();
}
// Export a module-scoped MongoClient promise. By doing this in a
// separate module, the client can be shared across functions.
export default clientPromise;
**Post.tsx **
import * as mongoose from "mongoose";
import Joi from "joi";
type post = {
name: string;
prompt: string;
photo: string;
};
const PostSchema = new mongoose.Schema({
name: { type: String, required: true },
prompt: { type: String, required: true },
photo: { type: String, required: true },
});
function validatePost(data: post) {
const schema = Joi.object({
name: Joi.string().min(1).max(100).required(),
prompt: Joi.string().min(2).required(),
photo: Joi.string().min(0).required(),
});
return schema.validate(data);
}
const Post = mongoose.model("post") || mongoose.model("post", PostSchema);
export { validatePost };
export default Post;
**
Where i called post modal**
import type { NextApiRequest, NextApiResponse } from "next";
import clientPromise from "#/lib/mongodb/mongodb";
import { v2 as cloudinary } from "cloudinary";
import Post from "#/lib/mongodb/models/post";
import { validatePost } from "#/lib/mongodb/models/post";
cloudinary.config({
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET,
});
export const config = {
api: {
bodyParser: {
sizeLimit: "50mb",
},
responseLimit: false,
},
};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
await clientPromise;
if (req.method === "GET") {
try {
const posts = await Post.find({});
res.status(200).json({ success: true, data: posts });
} catch (err) {
res.status(500).json({
success: false,
message: "Fetching posts failed, please try again",
});
}
} else if (req.method === "POST") {
try {
const { error } = validatePost(req.body);
if (error) return res.status(400).send(error.details[0].message);
const { name, prompt, photo } = req.body;
const photoUrl = await cloudinary.uploader.upload(photo);
const post = new Post({
name,
prompt,
photo: photoUrl.url,
});
const newPost = await post.save();
res.status(200).json({ success: true, data: newPost });
} catch (err) {
res.status(500).json({
success: false,
message: "Unable to create a post, please try again",
});
}
}
}
i am trying to save new document to mongo db, the Schema validation is not working for me, i am trying ti make required true, but i still can add new document without the required field.
this is my schema:
// lib/models/test.model.ts
import { Model, Schema } from 'mongoose';
import createModel from '../createModel';
interface ITest {
first_name: string;
last_name: string;
}
type TestModel = Model<ITest, {}>;
const testSchema = new Schema<ITest, TestModel>({
first_name: {
type: String,
required: [true, 'Required first name'],
},
last_name: {
type: String,
required: true,
},
});
const Test = createModel<ITest, TestModel>('tests', testSchema);
module.exports = Test;
this is createModel:
// lib/createModel.ts
import { Model, model, Schema } from 'mongoose';
// Simple Generic Function for reusability
// Feel free to modify however you like
export default function createModel<T, TModel = Model<T>>(
modelName: string,
schema: Schema<T>
): TModel {
let createdModel: TModel;
if (process.env.NODE_ENV === 'development') {
// In development mode, use a global variable so that the value
// is preserved across module reloads caused by HMR (Hot Module Replacement).
// #ts-ignore
if (!global[modelName]) {
createdModel = model<T, TModel>(modelName, schema);
// #ts-ignore
global[modelName] = createdModel;
}
// #ts-ignore
createdModel = global[modelName];
} else {
// In production mode, it's best to not use a global variable.
createdModel = model<T, TModel>(modelName, schema);
}
return createdModel;
}
and this is my tests file:
import { connection } from 'mongoose';
import type { NextApiRequest, NextApiResponse } from 'next';
const Test = require('../../../lib/models/test.model');
import { connect } from '../../../lib/dbConnect';
const ObjectId = require('mongodb').ObjectId;
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
switch (req.method) {
case 'POST': {
return addPost(req, res);
}
}
}
async function addPost(req: NextApiRequest, res: NextApiResponse) {
try {
connect();
// const { first_name, last_name } = req.body;
const test = new Test({
first_name: req.body.first_name,
last_name: req.body.last_name,
});
let post = await test.save();
// return the posts
return res.json({
message: JSON.parse(JSON.stringify(post)),
success: true,
});
// Erase test data after use
//connection.db.dropCollection(testModel.collection.collectionName);
} catch (err) {
//res.status(400).json(err);
res.status(400).json({
message: err,
success: false,
});
}
}
in the Postman, i send a request body without the required field (first_name) and i still can add it.
any help?
I am trying to use Pulumi to create an AWS Lambda that manipulates a DynamoDB table and is triggered by an API Gateway HTTP request.
My configuration works perfectly when I run pulumi up, but when I run Vitest, my test passes but exits with non-zero and this message:
⎯⎯⎯ Unhandled Rejection ⎯⎯⎯
Error: Could not find property info for real property on object: sdk
I can see that the error comes from this code in Pulumi, but I can't figure out what causes it. Am I doing something wrong or is this a bug (in which case I can create an issue)?
Below is a summary that I think has all the relevant info, but there is a minimal repo demonstrating the problem here (GitHub actions fail with the problem I'm describing).
I have an index.ts file that creates a database, gateway, and lambda:
import * as aws from '#pulumi/aws'
import * as apigateway from '#pulumi/aws-apigateway'
import handler from './handler'
const table = new aws.dynamodb.Table('Table', {...})
const tableAccessPolicy = new aws.iam.Policy('DbAccessPolicy', {
// removed for brevity. Allows put, get, delete
})
const lambdaRole = new aws.iam.Role('lambdaRole', {...})
new aws.iam.RolePolicyAttachment('RolePolicyAttachment', {...})
const callbackFunction = new aws.lambda.CallbackFunction(
'callbackFunction',
{
role: lambdaRole,
callback: handler(table.name),
}
)
const api = new apigateway.RestAPI('api', {
routes: [{
method: 'GET',
path: '/',
eventHandler: callbackFunction,
}]
})
export const dbTable = table
export const url = api.url
The handler is imported from a separate file:
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import * as pulumi from '#pulumi/pulumi';
import * as aws from '#pulumi/aws';
export default function (tableName: pulumi.Output<string>) {
return async function handleDocument(
event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> {
try {
const client = new aws.sdk.DynamoDB.DocumentClient();
await client
.put({
TableName: tableName.get(),
Item: { PK: 'hello', roomId: '12345' },
})
.promise();
const result = await client
.get({
TableName: tableName.get(),
Key: { PK: 'hello' },
})
.promise();
await client
.delete({
TableName: tableName.get(),
Key: { PK: 'hello' },
})
.promise();
return {
statusCode: 200,
body: JSON.stringify({
item: result.Item,
}),
};
} catch (err) {
return {
statusCode: 200,
body: JSON.stringify({
error: err,
}),
};
}
};
}
Finally, I have a simple test:
import * as pulumi from '#pulumi/pulumi';
import { describe, it, expect, beforeAll } from 'vitest';
pulumi.runtime.setMocks(
{
newResource: function (args: pulumi.runtime.MockResourceArgs): {
id: string;
state: Record<string, any>;
} {
return {
id: `${args.name}_id`,
state: args.inputs,
};
},
call: function (args: pulumi.runtime.MockCallArgs) {
return args.inputs;
},
},
'project',
'stack',
false
);
describe('infrastructure', () => {
let infra: typeof import('./index');
beforeAll(async function () {
// It's important to import the program _after_ the mocks are defined.
infra = await import('./index');
});
it('Creates a DynamoDB table', async () => {
const tableId = await new Promise((resolve) => {
infra?.dbTable?.id.apply((id) => resolve(id));
});
expect(tableId).toBe('Table_id');
});
});
Your function is importing the Pulumi SDK, and you're trying to set the table name as a pulumi.Output<string>
Using the Pulumi SDK inside a lambda function isn't recommended or support.
I would recommend removing the Pulumi dependency from your function
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
export default function (tableName: string) {
return async function handleDocument(
event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> {
try {
const client = new aws.sdk.DynamoDB.DocumentClient();
await client
.put({
TableName: tableName.get(),
Item: { PK: 'hello', roomId: '12345' },
})
.promise();
const result = await client
.get({
TableName: tableName.get(),
Key: { PK: 'hello' },
})
.promise();
await client
.delete({
TableName: tableName.get(),
Key: { PK: 'hello' },
})
.promise();
return {
statusCode: 200,
body: JSON.stringify({
item: result.Item,
}),
};
} catch (err) {
return {
statusCode: 200,
body: JSON.stringify({
error: err,
}),
};
}
};
}
The callback function should take take non inputty types, which should then remove the need to call the Pulumi SDK during your test suite. You can see an example here:
https://github.com/pulumi/examples/blob/258d3bad0a00020704743e37911c51be63c06bb4/aws-ts-lambda-efs/index.ts#L32-L40
I'm working with NestJS with GraphQL and MongoDB.
I'm trying store image files using GridFS using mongo-gridfs package.
Uploading images to database works fine, but how can I access to this files?
I mean for example I want to get source path of this files and use it in my frontend
Here is my resolver and service:
// photo.resolver.ts
import { Resolver, Mutation, Query, Args } from '#nestjs/graphql';
import { GraphQLUpload, FileUpload } from 'graphql-upload';
import { Photo } from './dto/photo.dto';
import { PhotoService } from './services/photo.service';
#Resolver()
export class PhotoResolver {
constructor(
private readonly photoService: PhotoService
) {}
#Query(() => ???, { nullable: true })
async photo(#Args('id', { nullable: true }) id: string) {
const photo = await this.photoService.findOne(id);
return ???;
}
#Mutation(() => Photo)
async uploadPhoto(#Args({name: 'file', type: () => GraphQLUpload}) file: FileUpload) {
return await this.photoService.save(file);
}
}
// photo.service.ts
import { Connection } from 'mongoose';
import { Injectable } from '#nestjs/common';
import { InjectConnection } from '#nestjs/mongoose';
import { FileUpload } from 'graphql-upload';
import { MongoGridFS } from 'mongo-gridfs';
import { Photo } from '../photo.interface';
#Injectable()
export class PhotoService {
private fileModel: MongoGridFS;
constructor(
#InjectConnection() private readonly connection: Connection
) {
this.fileModel = new MongoGridFS(this.connection.db as any, 'photo');
}
async findOne(id: string) {
return await this.fileModel.findById(id);
}
async save(file: FileUpload): Promise<Photo> {
return await this.fileModel.writeFileStream(file.createReadStream(), {
filename: file.filename,
contentType: file.mimetype
});
}
}
I've tried two approached:
I used downloadFile method from my photoModel, but it returns path to this file in my Temp directory in local disk.
// photo.service.ts
async findOne(id: string): Promise<string> {
return await this.fileModel.downloadFile(id); // C:\...\AppData\Local\Temp\189450ef
}
// photo.resolver.ts
#Query(() => String, { nullable: true })
async photo(#Args('id', { nullable: true }) id: string) {
return id && await this.photoService.findOne(id);
}
It works per se but it doesn't look to me as a proper solution. I'd prefer that source path should "lead" to my server.
I used readFileStream method from my photoModel, which return filestream and added #Res() res to arguments in resolver.
// photo.service.ts
async findOne(id: string): Promise<GridFSBucketReadStream> {
return await this.fileModel.readFileStream(id);
}
// photo.resolver.ts
#Query(() => Boolean)
async photo(#Args('id', { nullable: true }) id: string, #Res() res) {
const photoStream = await this.photoService.findOne(id);
photoStream.pipe(res);
return true;
}
And now I've got such an error in terminal:
[Nest] 12408 - 10.07.2021, 13:02:25 [ExceptionsHandler] dest.on is not a function +27555ms
TypeError: dest.on is not a function
The title of this question is just the error I am currently receiving, but what I really need help with is understanding observables and API calls. For whatever reason, I just haven't been able to get a good grasp of this concept, and I am hoping that someone might have an explanation that will finally click.
I am trying to create a new Angular service that retrieves JSON from an API. I then need to map the response to a model. Due to weird naming conventions, job descriptions and job requirements are used interchangeably here. Here is my service class.
import { CommunicationService } from './communication.service';
import { AiDescription } from '../models/ai-description.model';
import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
#Injectable()
export class AiDescriptionService {
requirements: Observable<AiDescription[]>;
private aiDescriptionUrl: string = '/api/core/company/jobdescriptions';
private dataStore: {
requirements: AiDescription[]
};
private _requirements: BehaviorSubject<AiDescription[]>;
private emptyRequestParams = {
"company_id": "",
"carotene_id": "",
"carotene_version": "",
"city": "",
"state": "",
"country": ""
};
readonly caroteneVersion: string = "caroteneV3";
constructor(
private communicationService: CommunicationService
) {
this.dataStore = { requirements: [] };
this._requirements = new BehaviorSubject<AiDescription[]>([]);
this.requirements = this._requirements.asObservable();
}
LoadRequirements(params: Object) {
this.communicationService.postData(this.aiDescriptionUrl, params)
.subscribe(res => {
let jobDescriptions = [];
jobDescriptions = res.jobdescriptions;
jobDescriptions.forEach((desc: { id: string; description: string; }) => {
let aiDescription = new AiDescription();
aiDescription.id = desc.id;
aiDescription.description = desc.description;
});
this.dataStore.requirements = res;
this._requirements.next(Object.assign({}, this.dataStore).requirements);
});
}
CreateRequest(
companyID : string,
caroteneID : string,
city: string,
state: string,
country: string
): Object {
let newRequestParams = this.emptyRequestParams;
newRequestParams.company_id = companyID;
newRequestParams.carotene_id = caroteneID;
newRequestParams.carotene_version = this.caroteneVersion;
newRequestParams.city = city;
newRequestParams.state = state;
newRequestParams.country = country;
this.LoadRequirements(newRequestParams);
return this.dataStore;
}
}
The postData() function being called by this.communicationService is here:
postData(url: string, jobInformation: any): Observable<any> {
const start = new Date();
const headers = new HttpHeaders({ 'Content-Type': 'application/json' });
const body = JSON.stringify(jobInformation);
const options = { headers };
return this.http.post(url, body, options)
.catch(err => Observable.throw(err))
.do(() => {
this.analyticsLoggingService.TrackTiming('JobPostingService', 'PostSuccess', new Date().getTime() - start.getTime());
}, () => {
this.analyticsLoggingService.TrackError('JobPostingService', 'PostFailure');
});
}
I didn't write the postData function, and I would not be able to modify it. When running a unit test, I am getting this error: "TypeError: Cannot read property 'forEach' of undefined".
But more than simply fixing the error, I am really trying to get a better understanding of using Observables, which is something I haven't been able to get a good understanding of from other sources.
In your example, I recommend replacing any and Object with explicitly defined models.
Here's an example for Angular 8 for Subscription, Promise, and Observable API calls. You can get more info here: https://angular.io/tutorial/toh-pt6.
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '#angular/common/http';
import { Observable } from 'rxjs';
import { User } from './user.model';
#Injectable({ providedIn: 'root' })
export class UserService {
users: User[];
authHeaders = new HttpHeaders()
.set('Content-Type', 'application/json');
constructor(
private readonly http: HttpClient
) { }
getUsers() {
this.http.get(`https://myApi/users`, { headers: this.authHeaders })
.subscribe(
(data: User[]) => {
this.users = data;
}, (error: HttpErrorResponse) => { /* handle error */ });
}
async getUserPromise(userID: number): Promise<User> {
const url = `https://myApi/users/${userID}`;
return this.http.get<User>(url, { headers: this.authHeaders })
.toPromise();
}
getUserObservable(userID: number): Observable<User> {
const url = `https://myApi/users/${userID}`;
return this.http.get<User>(url, { headers: this.authHeaders });
}
}
I like to keep my class models in separate files. This example would have user.model.ts with content like:
export class User {
constructor(
public id: number,
public username: string,
public displayName: string,
public email: string
) { }
}
I've not included authentication headers or error handling for brevity; however, you might want to add those as needed.