Loopback 4 REST Controller path returns NotFoundError 404 - loopbackjs

Created a REST Controller with CRUD functions object via the CLI using
lb4 controller media
pointing to an existing MediaRepository for an existing Entity Media model
both of which were generated using the lb4 CLI as well.
A MediaController class was created with all of the REST routes for /media*
The /ping route works fine so I looked for any special routing configuration for it to see if there might be a config messing for /media but nothing was obvious.
An HTTP Get request to /media response with a web page having the following content:
<h1>NotFoundError</h1>
<h2><em>404</em> Endpoint "GET /media" not found.</h2>
There is probably some fundamental configuration or setup that needs to happen but I am just not seeing it.
MediaController class
import {
Count,
CountSchema,
Filter,
repository,
Where,
} from '#loopback/repository';
import {
post,
param,
get,
getFilterSchemaFor,
getWhereSchemaFor,
patch,
put,
del,
requestBody, Request, RestBindings, ResponseObject
} from '#loopback/rest';
import { Media } from '../models';
import { MediaRepository } from '../repositories';
export class MediaController {
constructor(
#repository(MediaRepository)
public mediaRepository: MediaRepository,
) { }
#post('/media', {
responses: {
'200': {
description: 'Media model instance',
content: { 'application/json': { schema: { 'x-ts-type': Media } } },
},
},
})
async create(#requestBody() media: Media): Promise<Media> {
return await this.mediaRepository.create(media);
}
#get('/media/count', {
responses: {
'200': {
description: 'Media model count',
content: { 'application/json': { schema: CountSchema } },
},
},
})
async count(
#param.query.object('where', getWhereSchemaFor(Media)) where?: Where<Media>,
): Promise<Count> {
return await this.mediaRepository.count();
}
#get('/media', {
responses: {
'200': {
description: 'Array of Media model instances',
content: {
'application/json': {
schema: { type: 'array', items: { 'x-ts-type': Media } },
},
},
},
},
})
async find(
#param.query.object('filter', getFilterSchemaFor(Media)) filter?: Filter<Media>,
): Promise<Media[]> {
return await this.mediaRepository.find(filter);
}
#patch('/media', {
responses: {
'200': {
description: 'Media PATCH success count',
content: { 'application/json': { schema: CountSchema } },
},
},
})
async updateAll(
#requestBody() media: Media,
#param.query.object('where', getWhereSchemaFor(Media)) where?: Where<Media>,
): Promise<Count> {
return await this.mediaRepository.updateAll(media, where);
}
#get('/media/{id}', {
responses: {
'200': {
description: 'Media model instance',
content: { 'application/json': { schema: { 'x-ts-type': Media } } },
},
},
})
async findById(#param.path.string('id') id: string): Promise<Media> {
return await this.mediaRepository.findById(id);
}
#patch('/media/{id}', {
responses: {
'204': {
description: 'Media PATCH success',
},
},
})
async updateById(
#param.path.string('id') id: string,
#requestBody() media: Media,
): Promise<void> {
await this.mediaRepository.updateById(id, media);
}
#put('/media/{id}', {
responses: {
'204': {
description: 'Media PUT success',
},
},
})
async replaceById(
#param.path.string('id') id: string,
#requestBody() media: Media,
): Promise<void> {
await this.mediaRepository.replaceById(id, media);
}
#del('/media/{id}', {
responses: {
'204': {
description: 'Media DELETE success',
},
},
})
async deleteById(#param.path.string('id') id: string): Promise<void> {
await this.mediaRepository.deleteById(id);
}
}

So I set lb4 aside for a while while I evaluated other frameworks.
Came back to my lb4 demo project today. No changes to anything since then. Started the application.
npm run start
Browsed to localhost:3000/media
To my surprise it returned a json response. Now my response array was empty and it should have returned something as there were documents in the mongodb datasource but that is a separate issue to figure out.

Related

AWS API Gateway Export is exporting an older version

I created an API Gateway with AWS CDK with a resource and a request model:
const api = new apigateway.RestApi(this, 'MyApiGateway', {
deploy: true,
retainDeployments: false
});
const createDto = api.addModel('CreateUserDto', {
modelName: 'CreateUserDto',
schema: ...
});
const users = api.root.addResource('users');
users.addMethod(
'POST',
createUserLambdaIntegration, {
operationName: 'Create User',
requestModels: {
'application/json': createDto
},
methodResponses: [
{
statusCode: '201'
}
]
}
);
then I changed it by adding a response model and changing the name of the request model:
const api = new apigateway.RestApi(this, 'MyApiGateway', {
deploy: true,
retainDeployments: false
});
const createUserRequest = api.addModel('CreateUserRequest', {
modelName: 'CreateUserRequest',
schema: ...
});
const createUserResponse = api.addModel('CreateUserResponse', {
modelName: 'CreateUserResponse',
schema: ...
});
const users = api.root.addResource('users');
users.addMethod(
'POST',
createUserLambdaIntegration, {
operationName: 'Create User',
requestModels: {
'application/json': createUserRequest
},
methodResponses: [
{
statusCode: '201',
responseModels: {
'application/json': createUserResponse
}
}
]
}
);
this all works somewhat fine, as I am able to see the updated models on the API Gateway console, but when I try exporting the API as a Swagger JSON, it always exports the first version I deployed. I tried exporting it using both the API Gateway console and the AWS CLI with the exact same result.
What am I doing wrong?

Stubbing / Spy on global.fetch in Deno

I'm just getting into Deno, one of the things I'm a little unsure about is how to stub or create a spy for the global fetch function?
One solution is to simply wrap the fetch in a function which itself can by stubbed or spied on, but that seems like an unnecessary abstraction.
Any help would be much appreciated.
With denock you can mock the return object of the fetch call. Maybe not what you want but now you can test without a real call to the server.
https://deno.land/x/denock#0.2.0
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
import { denock } from "https://deno.land/x/denock/mod.ts";
// function to test
async function fetchFromServer() {
const urlObject = new URL("https://jsonplaceholder.typicode.com/todos");
const response = await fetch(urlObject, {
method: "POST",
headers: new Headers({
"content-type": "application/json",
}),
body: JSON.stringify({
userId: 2,
id: 23024,
title: "delectus aut autem",
completed: false,
}),
});
return await response.json();
}
// mock return
denock({
method: "POST",
protocol: "https",
host: "jsonplaceholder.typicode.com",
headers: [
{
header: "content-type",
value: "application/json",
},
],
path: "/todos",
requestBody: {
userId: 2,
id: 23024,
title: "delectus aut autem",
completed: false,
},
replyStatus: 201,
responseBody: { example: "My mocked response" },
});
// test
Deno.test("fetch", async () => {
const actual = await fetchFromServer();
assertEquals({ example: "My mocked response" }, actual);
});

How to res.send something in loopback-next

I have a function that has a callback as shown below, and I want to return account that is returned in the callback as a response to the request for the function. How could I res.send the account (since I cannot return values from a callback function)
#get('/payments/retrieve-stripe/{id}', {
responses: {
'200': {
description: 'User model instance',
content: {'application/json': {schema: {'x-ts-type': User}}},
},
},
})
async retrieveStripe(#param.path.number('id') id: number,
#requestBody() req: any): Promise<any> {
if (!req.stripeAccountId) {
throw new HttpErrors.NotFound('No Stripe Account');
}
else {
stripe.accounts.retrieve(
req.stripeAccountId,
async function(err: any, account: any) {
//console.log(err)
console.log(account)
return account
})
}
}
If you're stuck using a callback any any point in your code you're going to use manual promises (or maybe some promise wrapping library).
Instead of using async and return, use resolve() which functionally can return from any point in your function, regardless of scope.
#get('/payments/retrieve-stripe/{id}', {
responses: {
'200': {
description: 'User model instance',
content: {'application/json': {schema: {'x-ts-type': User}}},
},
},
})
retrieveStripe(#param.path.number('id') id: number, #requestBody() req: any): Promise<any> {
return new Promise((resolve, reject) => {
if (!req.stripeAccountId) {
throw new HttpErrors.NotFound('No Stripe Account');
}
else {
stripe.accounts.retrieve(req.stripeAccountId, function(err: any, account: any) {
resolve(account);
})
}
});
}

How to return a callback's function return value

I'm using loopback-next along with the stripe api. In the stripe API, I call retrieve account as follows, in a payments.controller.ts file:
#post('/payments/retrieve-stripe/{id}', {
responses: {
'200': {
description: 'User model instance',
content: {'application/json': {schema: {'x-ts-type': User}}},
},
},
})
async retrieveStripe(#param.path.number('id') id: number,
#requestBody() req: any): Promise<any> {
console.log(req);
if (!req.stripeAccountId) {
throw new HttpErrors.NotFound('No Stripe Account');
}
else {
return await stripe.accounts.retrieve(
req.stripeAccountId,
function(err: any, account: any) {
return err ? err : account
})
}
}
However, when I try return account, nothing is returned in the JSON body. If i try, response.json on the front end, it said that the JSON unexpectedly finished, meaning there is nothing in the body. How would I be able to successfully return account in the above function inside of a controller function?
This was the same problem I had for trying to return a string as well. I'm not sure what to do.
EDIT: I learned that you cannot return variables in a callback and that is the issue.
You have to require type definitions (#types/stripe) to use its library in promise style. After that you can use in following way:-
#post('/payments/retrieve-stripe/{id}', {
responses: {
'200': {
description: 'User model instance',
content: { 'application/json': { schema: { type: 'object' } } },
},
},
})
async retrieveStripe(#param.path.number('id') id: number,
#requestBody() req: any): Promise<any> {
console.log(req);
if (!req.stripeAccountId) {
throw new HttpErrors.NotFound('No Stripe Account');
} else {
return await stripe.accounts.retrieve(req.stripeAccountId).then((res: any) => {
return res;
}).catch((err: any) => {
console.debug(err);
throw new HttpErrors.InternalServerError('Something went wrong!')
});
}
}
For more details https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/stripe/stripe-tests.ts

Loopback remote method not returning response body

So I created this remote method in loopback:
Message.findUserMessages = function(id,cb) {
Message.find({
where: {
from_user_id: id
},
include: {
"relation":"message_text"
}
});
};
Message.remoteMethod('findUserMessages', {
accepts: {
arg: 'id',
type: 'number'
},
returns: {
arg: 'response',
type: 'Object'
},
http: {
path: '/user/',
verb: 'get'
}
});
But when I view the response, it does not show the output in the response body. The only reason I know the correct results are being accessed is due to the fact that my DB is returning the result of the query. How do I get put the output of the query in the response body?
The correct code should be:
Message.findUserMessages = function(id, cb) {
Message.find({
where: {
from_user_id: id
},
include: {
"relation":"message_text"
}
}, function(err, response) {
if (err) throw err;
cb(null, response);
});
};
Message.remoteMethod('findUserMessages', {
accepts: {
arg: 'id',
type: 'number',
required: true,
http: { source: 'path' }
},
returns: {
arg: 'response',
type: 'Object',
root: true
},
http: {
path: '/user/:id/findUserMessages',
verb: 'get'
}
});
You forget to callback the response.
Note: I've also changed the http url path hoping you wanted it like so. And also source to the argument is set to path. You might also want to look at usage of root.