Why is there no other successful status codes in fastify route? - node.js

So I created a fastify route using fastify.route. But there is no way I could return a 201 statusCode in the response. And even if I return a 201 statusCode, it will be converted to a 200 one.
fastify.route({
method: 'GET',
url: '/',
schema: {
querystring: {
name: { type: 'string' },
excitement: { type: 'integer' }
},
response: {
200: {
type: 'object',
properties: {
hello: { type: 'string' }
}
}
}
},
handler: function (request, reply) {
reply.send({ hello: 'world' })
}
})
ΒΆ
EDIT
The code that I was referring to was that of swagger
response: {
200: {
type: 'object',
properties: {
hello: { type: 'string' }
}
}
}
Here I want to replace 200 with a 201, and gives the required fields, but seems like that is not throwing an error as imagined
This is messing up my post request.

Related

AJV validation doesn't return multiple errors when different values are missing in the fastify request body

I have a fastify server with a post endpoint. It accepts a JSON request body with a validation. Server code below:
const fastify = require("fastify");
const server = fastify({
ajv: {
customOptions: {
allErrors: true,
},
},
logger: true,
});
const schema = {
schema: {
body: {
type: "object",
properties: {
data: {
type: "array",
items: {
type: "object",
properties: {
foo: {
type: "string",
},
bar: {
type: "string",
},
},
required: ["foo", "bar"],
},
},
},
required: ["data"],
},
},
};
server.post("/", schema, function (request, reply) {
console.log({
request: {
body: JSON.stringify(request.body),
},
});
reply.send({ message: "hello" });
});
server.listen(3000, function (err, address) {
if (err) {
fastify.log.error(err);
process.exit(1);
}
console.log(`server listening on ${address}`);
});
Below is a valid request body.
{ "data":[{ "bar": "bar exists", "foo": "foo exists" }]}
When I try to access the same server with multiple values in input missing i.e.,
{ "data":[{ "bar": "bar exists, foo missing" }, {}] }
I am getting the below response.
{
"statusCode": 400,
"error": "Bad Request",
"message": "body.data[0] should have required property 'foo', body.data[1] should have required property 'foo', body.data[1] should have required property 'bar'"
}
I want to get each error separately, instead of getting a single large error message as this request can go very large. I have tried a bit of trial around the ajv options but couldn't find anything.
Any help is appreciated. Cheers :)
You need to have a custom parser after the error is caught.
In order to achieve this approach, there is a method called setErrorHandler.
According to Fastify documentation:
Set a function that will be called whenever an error happens.
This is a simple parser, but you may need to change it to your taste:
server.setErrorHandler(function (error, request, reply) {
if (error.validation) {
reply.status(400).send({
statusCode: error.statusCode,
error: 'Bad Request',
message: error.validation.map(err => `${err.instancePath} ${err.message}`)
});
}
})
// rest of code

Serverless Event object failed validation at createError

I am working on a serverless/nodejs12 project. The project deploys fine, but when I try to invoke the endpoint with postman I get the following error. I have browsed through many postings of similar errors but still failed to understand what's going on. Will appreciate any pointers.
Thank you
at createError (/var/task/src/handlers/webpack:/home/serverless-workspace/notification/node_modules/#middy/util/index.js:259:1)
at validatorMiddlewareBefore (/var/task/src/handlers/webpack:/home/serverless-workspace/notification/node_modules/#middy/validator/index.js:55:1)
at runMiddlewares (/var/task/src/handlers/webpack:/home/serverless-workspace/notification/node_modules/#middy/core/index.js:120:1)
at runRequest (/var/task/src/handlers/webpack:/home/serverless-workspace/notification/node_modules/#middy/core/index.js:80:1) {
details: [
{
instancePath: '/body',
schemaPath: '#/properties/body/type',
keyword: 'type',
params: [Object],
message: 'must be object'
}
]
createNotification.js
--------------------------
async function createNotification(event, context) {
const { title } = event.body;
const { destination } = event.body;
const { destinationType } = event.body
const notification = {
id: uuid(),
title,
destination,
destinationType,
status: 'OPEN',
createdAt: new Date().toISOString(),
}
try {
await dynamodb.put({
TableName: process.env.NOTIFY_TABLE_NAME,
Item: notification
}).promise();
} catch (error) {
console.error(error);
throw new createError.InternalServerError(error);
}
return {
statusCode: 200,
body: JSON.stringify(notification),
};
}
export const handler = commonMiddleware(createNotification)
.use(validator({ inputSchema: createNotificationSchema }));
and the schema
----------------------
const schema = {
type: 'object',
properties: {
body: {
type: 'object',
required: ['status'],
default: { status: 'OPEN' },
properties: {
status: {
default: 'OPEN',
enum: ['OPEN', 'CLOSED'],
},
},
},
},
required: ['body'],
};
export default schema;
You're getting the error must be object. I'm guessing your input looks like { event: { body: 'JSON string' } }. You'll need to use another middy middleware to parse the body prior to validating the input. Which middleware will depend on what AWS event it's expecting. Middy >3.0.0 supports all AWS events.

Fastify schema validation multipart/form-data (body should be object)

Multipart form data file uplaod time comming error body should be object, and i am using ajv plugin also, still i am using same issue. below is my reference code.
app.js
const fastify = require('fastify')({
logger: true
});
const Ajv = require('ajv');
const ajv = new Ajv({
useDefaults: true,
coerceTypes: true,
$data: true,
extendRefs: true
});
ajv.addKeyword("isFileType", {
compile: (schema, parent, it) => {
parent.type = "file";
delete parent.isFileType;
return () => true;
},
});
fastify.setSchemaCompiler((schema) => ajv.compile(schema));
routes.js
schema: {
tags: [{
name: 'Category'
}],
description: 'Post category data',
consumes: ['multipart/form-data'],
body: {
type: 'object',
isFileType: true,
properties: {
name: {
type: 'string'
},
thumb_url: {
isFileType: true,
type: 'object'
},
img_url: {
isFileType: true,
type: 'object'
},
status: {
type: 'number',
enum: [0, 1],
default: 1
}
},
required: ['name', 'thumb_url', 'img_url']
},
response: {
201: {
type: 'object',
properties: categoryProperties
}
}
}
response
{
"statusCode": 400,
"error": "Bad Request",
"message": "body should be object"
}
I suspect the error is in the way I send the data, but I am having trouble figuring it out. I have read about this error and it seems to be generated when an object is passed to formData, but I am sending a string so I don't understand why it happens. thanks in advance!
I think your configuration to manage multipart is wrong and the schema should be fixed as this working example:
const fastify = require('fastify')({ logger: true })
fastify.register(require('fastify-multipart'), {
addToBody: true
})
const Ajv = require('ajv')
const ajv = new Ajv({
useDefaults: true,
coerceTypes: true,
$data: true,
extendRefs: true
})
ajv.addKeyword('isFileType', {
compile: (schema, parent, it) => {
parent.type = 'file'
delete parent.isFileType
return () => true
}
})
fastify.setSchemaCompiler((schema) => ajv.compile(schema))
fastify.post('/', {
schema: {
tags: [{
name: 'Category'
}],
description: 'Post category data',
consumes: ['multipart/form-data'],
body: {
type: 'object',
properties: {
name: { type: 'string' },
thumb_url: { isFileType: true },
img_url: { isFileType: true },
status: {
type: 'number',
enum: [0, 1],
default: 1
}
},
required: ['name', 'thumb_url', 'img_url']
}
}
}, async (req, reply) => {
let filepath = path.join(__dirname, `${req.body.thumb_url[0].filename}-${Date.now()}`)
await fs.writeFile(filepath, (req.body.thumb_url[0].data))
filepath = path.join(__dirname, `${req.body.img_url[0].filename}-${Date.now()}`)
await fs.writeFile(filepath, (req.body.img_url[0].data))
return req.body
})
fastify.listen(3000)
Call it with this request:
curl -X POST \
http://127.0.0.1:3000/ \
-H 'content-type: multipart/form-data' \
-F 'thumb_url=#/home/wks/example-file' \
-F 'img_url=#/home/wks/example-file' \
-F 'name=fooo'
You will get:
{
"thumb_url":[
{
"data":{
"type":"Buffer",
"data":[
97,
115,
100,
10
]
},
"filename":"example-file",
"encoding":"7bit",
"mimetype":"application/octet-stream",
"limit":false
}
],
"img_url":[
{
"data":{
"type":"Buffer",
"data":[
97,
115,
100,
10
]
},
"filename":"example-file",
"encoding":"7bit",
"mimetype":"application/octet-stream",
"limit":false
}
],
"name":"foo",
"status":1
}

Provisional headers are shown

I am using Extjs in web and node.js in the api side. I called the api defined, from the web. I passed params and required data as request, but its showing the below error and request is unsuccessful.
Here is the api call code:
Ext.Ajax.request({
url: "http://localhost:3000/update-fund",
method: "POST",
dataType: 'json',
params: {
userId: userDocGlobal.id
},
headers: {
'Content-Type': 'application/json'
},
jsonData: {
fundId: PECalculator.selectedFund,
fundDate: getAiqDateFromPicker(Ext.getCmp("currentCalculationDateFundLevel")),
fundData: fund,
ownerId: userDocGlobal.companyid,
lpId: userDocGlobal.companyid,
role: userDocGlobal.role,
userId: userDocGlobal.id,
companyid: userDocGlobal.companyid
},
success: function (response) {
if (callback) {
callback(response);
}
if(userRole === "FREE"){
moduleObj.setFundNameAndCalcDateForFreeUser();
}
},
error: function () {
if (errorCallback) {
errorCallback();
}
}
})

Returning XML in response from Loopback Remote Method

I am using the Loopback Connector REST (1.9.0) and have a remote method that returns XML:
Foo.remoteMethod
(
"getXml",
{
accepts: [
{arg: 'id', type: 'string', required: true }
],
http: {path: '/:id/content', "verb": 'get'},
returns: {"type": "string", root:true},
rest: {"after": setContentType("text/xml") }
}
)
The response always returns an escaped JSON string:
"<foo xmlns=\"bar\"/>"
instead of
<foo xmlns="bar"/>
Note that the response does have the content-type set to text/xml.
If I set my Accept: header to "text/xml", I always get "Not Acceptable" as a response.
If I set
"rest": {
"normalizeHttpPath": false,
"xml": true
}
In config.json, then I get a 500 error:
SyntaxError: Unexpected token <
I think that the "xml: true" property is simply causing a response parser to try to convert JSON into XML.
How can I get Loopback to return the XML in the response without parsing it? Is the problem that I am setting the return type to "string"? If so, what it the type that Loopback would recognize as XML?
You need to set toXML in your response object (more on that in a bit). First, set the return type to 'object' as shown below:
Foo.remoteMethod
(
"getXml",
{
accepts: [
{arg: 'id', type: 'string', required: true }
],
http: {path: '/:id/content', "verb": 'get'},
returns: {"type": "object", root:true},
rest: {"after": setContentType("text/xml") }
}
)
Next, you will need to return a JSON object with toXML as the only property. toXML should be a function that returns a string representation of your XML. If you set the Accept header to "text/xml" then the response should be XML. See below:
Foo.getXml = function(cb) {
cb(null, {
toXML: function() {
return '<something></something>';
}
});
};
You still need to enable XML in config.json because it's disabled by default:
"remoting": {
"rest": {
"normalizeHttpPath": false,
"xml": true
}
}
See https://github.com/strongloop/strong-remoting/blob/4b74a612d3c969d15e3dcc4a6d978aa0514cd9e6/lib/http-context.js#L393 for more info on how this works under the hood.
I recently ran into this with trying to create a dynamic response for Twilio voice calling in Loopback 3.x. In the model.remoteMethod call, explicitly specify the return type and content type as follows:
model.remoteMethod(
"voiceResponse", {
accepts: [{
arg: 'envelopeId',
type: 'number',
required: true
}],
http: {
path: '/speak/:envelopeId',
"verb": 'post'
},
returns: [{
arg: 'body',
type: 'file',
root: true
}, {
arg: 'Content-Type',
type: 'string',
http: {
target: 'header'
}
}],
}
)
Have your method return both the xml and the content type via the callback function. Note that the first argument in the callback is to return an error if one exists:
model.voiceResponse = function(messageEnvelopeId, callback) {
app.models.messageEnvelope.findById(messageEnvelopeId, {
include: ['messages']
}).then(function(res) {
let voiceResponse = new VoiceResponse();
voiceResponse.say({
voice: 'woman',
language: 'en'
},
"This is a notification alert from your grow system. " + res.toJSON().messages.message
)
callback(null,
voiceResponse.toString(), // returns xml document as a string
'text/xml'
);
});
}

Resources