I want to add a custom schema formatter for fastify.
import fastify from 'fastify'
import AjvCompiler from '#fastify/ajv-compiler'
const ajvFormatter = AjvCompiler(ajv);
ajvFormatter.addFormat('new-format', /hello/);
const app = fastify({
schemaController: {
compilersFactory: {
buildValidator: ajvFormatter
}
}
})
I add the format but still gives the error:
Failed building the validation schema for POST: /hey, due to error unknown format
"new-format" ignored in schema at path
I guess latest fastify does not support this functionality.
You are using in the wrong way the #fastify/ajv-compiler module. It does not accept an ajv input parameter at all. not it exports an addFormat method.
You need to use the customOption option:
const fastify = require('fastify')
const app = fastify({
logger: true,
ajv: {
customOptions: {
formats: {
'new-format': /hello/,
},
},
},
})
app.get(
'/:hello',
{
schema: {
params: {
type: 'object',
properties: {
hello: {
type: 'string',
format: 'new-format',
},
},
},
},
},
async (request, reply) => {
return request.params
}
)
app.listen(8080, '0.0.0.0')
// curl http://127.0.0.1:8080/hello
// curl http://127.0.0.1:8080/hello-foo
Related
Strapi doesn't have any endpoint to get random data for this purpose you should write some custom code for your endpoint
custom route for that endpoint you want
// path: ./src/api/[your-endpiont]/routes/[custom-route].js
module.exports = {
"routes": [
{
"method": "GET",
"path": "/[your-endpiont]/random", // you can define everything you want for url endpoint
"handler": "[your-endpiont].random", // random is defined as a method
"config": {
"policies": []
}
}
]
}
now you have to run yarn develop or npm ... to display a random method in your strapi panel
Save this setting and retry to reach the random endpoint.
create a function as a service for getting random data in your endpoint API services.
// path: ./src/api/[your-endpiont]/services/[your-endpiont].js
'use strict';
/**
* news-list service.
*/
const { createCoreService } = require('#strapi/strapi').factories;
module.exports = createCoreService('api::news-list.news-list', ({ strapi }) => ({
async serviceGetRandom({ locale, id_nin }) { // these parametrs come from query
function getRandomElementsFromArray(array, numberOfRandomElementsToExtract = 1) {
const elements = [];
function getRandomElement(arr) {
if (elements.length < numberOfRandomElementsToExtract) {
const index = Math.floor(Math.random() * arr.length)
const element = arr.splice(index, 1)[0];
elements.push(element)
return getRandomElement(arr)
} else {
return elements
}
}
return getRandomElement([...array])
}
const newsListArray = await strapi
.db
.query("api::news-list.news-list")
.findMany({
where: {
locale: locale, // if you have multi-language data
$not: {
id: id_nin, // depend on where this endpoint API use
},
publishedAt: {
$notNull: true,
},
},
sort: [{ datetime: 'asc' }],
limit: 10,
populate: {
content: {
populate: {
thumbnail: true,
},
},
},
//? filter object throws an error when you used populate object, everything you want to filter properly best write into where{}
// filters: {
// publishedAt: {
// $notNull: true,
// },
// locale: locale
// }
})
if (!newsListArray.length) {
return null
}
return getRandomElementsFromArray(newsListArray, 2)
}
}));
explain code:
Strapi provides a Query Engine API to interact with the database layer at a lower level
strapi.db.query("api::news-list.news-list").findMany({})
The Query Engine allows operations on database entries,
I wrote this for my purpose probably you should change based on what you needed
{
where: {
locale: locale,
$not: {
id: id_nin
},
publishedAt: {
$notNull: true,
},
},
sort: [{ datetime: 'asc' }],
limit: 10,
populate: {
content: {
populate: {
thumbnail: true,
},
},
}
}
when you get data from your query, passed it to that function getRandomElementsFromArray(newsListArray, 2) to get some random item (how many random items do you want ? pass the second parameter)
At least if your array is null return null otherwise return data
create the controller
Controllers are JavaScript files that contain a set of methods, called actions, reached by the client according to the requested route so we going to call our services in this section
// path: ./src/api/[your-endpoint]/controllers/[your-endpoint].js
'use strict';
/**
* news-list controller
*/
const { createCoreController } = require('#strapi/strapi').factories;
module.exports = createCoreController('api::news-list.news-list', ({ strapi }) => ({
async random(ctx) { // name of this methods related to something we define in route ("handler": "[your-endpiont].random",)
const entity = await strapi.service('api::news-list.news-list').serviceGetRandom(ctx.query) // call our services, you can send all query you get from url endpoint (notice that you should write your endpoint api in strapi.service("your-endpoint"))
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
return this.transformResponse(sanitizedEntity);
// console.log(entity);
}
}));
I call this endpoint in my project nextjs & stapi cms
export const getRandomNewsItem = (id, locale) => {
return API
.get(`/news-list/random?locale=${locale}&id_nin=${id}`)
.then(res => res.data);
};
That's it, I'll hope you all get what to do
all resources you need
https://docs.strapi.io/developer-docs/latest/development/backend-customization/routes.html#creating-custom-routers
https://docs.strapi.io/developer-docs/latest/development/backend-customization/services.html#implementation
https://docs.strapi.io/developer-docs/latest/development/backend-customization/controllers.html#adding-a-new-controller
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/query-engine-api.html
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/query-engine/filtering.html#and
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/entity-service/order-pagination.html#ordering
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/entity-service/order-pagination.html#ordering
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/query-engine/populating.html
I'm trying to implement type inference from JSON schema in Fastify.
This example from the documentation doesn't work for me, as request.query remains unknown or sometimes {}.
Here is a direct copy-paste of the documentation on stackblitz. As you can see the Typescript compilation fails.
Any idea ?
The Querystring type is not defined hence the error. To define it do like the following:
import { JsonSchemaToTsProvider } from '#fastify/type-provider-json-schema-to-ts';
import fastify from 'fastify';
const server = fastify().withTypeProvider<JsonSchemaToTsProvider>();
server.get<{ Querystring: { foo: number, bar: string } }>(
'/route',
{
schema: {
querystring: {
type: 'object',
properties: {
foo: { type: 'number' },
bar: { type: 'string' },
},
required: ['foo', 'bar'],
},
} as const, // don't forget to use const !
},
(request, reply) => {
const { foo, bar } = request.query; // type safe!
}
);
Or, to keep it DRY install the #sinclair/typebox package and define it beforehand like:
import { JsonSchemaToTsProvider } from '#fastify/type-provider-json-schema-to-ts';
import fastify from 'fastify';
import { Static, Type } from '#sinclair/typebox';
const server = fastify().withTypeProvider<JsonSchemaToTsProvider>();
const getQuerystring = Type.Object({
foo: Type.Number(),
bar: Type.String(),
})
type GetQuerystring = Static<typeof getQuerystring>
server.get<{ Querystring: GetQuerystring }>(
'/route',
{
schema: {
querystring: getQuerystring,
} as const, // don't forget to use const !
},
(request, reply) => {
const { foo, bar } = request.query; // type safe!
}
);
Fastify has some really awesome json schema support. (Link)
However, I now want to use the schemas which I added with fastify.addSchema(..) inside my business logic as well. For example (pseudo code):
schema = fastify.getSchema("schema1")
if (schema.validate(data)) {
console.log("ok");
} else {
console.log("not ok");
}
How can I achieve that?
Right now, in Fastify, one route has a set of validation functions.
These functions exist only because you set them in the { schema: {} } route
configuration.
So, in the first place, if you don't set those schemas in a route, you will be not able to access them.
The getSchema function retrieves the schema object, not che compiled function.
The relation is not 1:1 because a validation function may use more schemas via the $ref keyword.
The only way to archive what you need is to monkey patch the internal Fastify (highly discouraged)
Or open a feature request to the project.
Here an example, and as you can see, you are limited to get the route's validation functions inside the route's context.
So, it is far from being a flexible usage.
const fastify = require('fastify')({ logger: true })
const {
kSchemaBody: bodySchema
} = require('fastify/lib/symbols')
fastify.post('/', {
schema: {
body: {
$id: '#schema1',
type: 'object',
properties: {
bar: { type: 'number' }
}
}
}
}, async (request, reply) => {
const schemaValidator = request.context[bodySchema]
const result = schemaValidator({ bar: 'not a number' })
if (result) {
return true
}
return schemaValidator.errors
})
fastify.inject({
method: 'POST',
url: '/',
payload: {
bar: 33
}
}, (err, res) => {
console.log(res.json())
})
Issue
I am working on file upload operation on new hapiJS framework with the latest hapi-swagger version, The older one is supported correctly but now I facing some problem while I am using hapi-swagger payload type form.
Is there something I missed?
Used libraries
hapi-hapi 19.1.1
hapi-Joi: 17.1.1
Hapi-swagger: 13.0.2
Node: 12.18.1
const Joi = require("#hapi/joi");
// Route file
app.route({
method: 'POST',
path: '/fileUpload/{format}',
options: Controller.fileUpload()
});
// Controller file
public fileUpload(): Hapi.RouteOptions{
return {
handler: async (request: any, h: Hapi.ResponseToolkit) => {
try {
let file = request.payload.file
let data = request.payload.data
let fileFormat = request.params.format
//some logic
} catch (err) {
throw error;
}
},
tags: ['api', 'fileUpload', 'POST'],
description: 'file uploading',
plugins: {
'hapi-swagger': {
payloadType: 'form'
}
},
validate: {
params: Joi.object({
format: Joi.string().required().description('Format of file').example('docx, xml or html').allow('docx', 'xml', 'html')
}),
payload: Joi.object({
data: Joi.object({
objectData: Joi.object({
objectName: Joi.string().required().description('Name of object').example('xyz').disallow('null', 'undefined'),
objectRID: Joi.string().optional().example('#24:6').disallow('null', 'undefined')
}).required(),
objectTravelRelations: Joi.array().optional().items(
Joi.object({
objectName: Joi.string().required().description('Class of Object').disallow('null', 'undefined'),
objectUID: Joi.string().required().example('873cc6cd-d51d-44d0-8377-2061641a11a3').disallow('null', 'undefined'),
relation: Joi.string().required().description('Name of the relation').example('HasChild').disallow('null', 'undefined'),
direction: Joi.string().required().description('Direction of relation').example('in').allow('in', 'out').disallow('null', 'undefined')
}).required()
)
}),
file: Joi.any()
.meta({ swaggerType: 'file' }).required()
.description('file.')
}),
options: {
allowUnknown: true
},
failAction: async (request, h, error) => {
throw error;
},
},
payload: {
output: 'stream',
parse: true,
multipart: true,
maxBytes: 10485760
}
};
}
Expected Behavior
The form data is validated and the file upload successfully.
Actual Behavior
data has treated a string but actually its an object
message: ' "data" must be of type object '
I am digging into moleculer.js the only thing i am finding difficult to understand;how to get parameters inside actions of a service
below given is my code
const ApiGateway = require("moleculer-web");
module.exports = {
name: "api",
mixins: [ApiGateway],
settings: {
port: process.env.PORT || 3000,
bodyParsers: {
json: true,
urlencoded: { extended: true }
},
routes: [{
path: "/api",
whitelist: [
"**"
]
}],
assets: {
folder: "public"
}
},
};
Below is my user service where i want to get post parameters
module.exports = {
name: "users",
dependencies: ["guard"],
actions: {
create: {
restricted: [
"api"
],
async handler(ctx,route, req, res) {
this.logger.info(req);
this.logger.info("'users.create' has been called.");
const token=await ctx.call("guard.generate",{service:"abc"});
what i want is
const token=await ctx.call("guard.generate",{service:req.body.name});
instead of
const token=await ctx.call("guard.generate",{service:"abc"});
const verify=await ctx.call("guard.check",{token:token});
return [token,verify,req];
}
},
}
MoleculerĀ“s Actions has the following signature: <actionName> (ctx) {// logic} or <actionName>: { handler (ctx) { // logic}}.
So what you have to do is this:
module.exports = {
name: "users",
actions: {
welcome: {
handler(ctx) {
console.log(ctx.params) // Print the request params
// Call other actions ctx.call('serviceName.actionName`, ...data...)
return ctx.params
}
}
}
}
More info about Actions: https://moleculer.services/docs/0.13/actions.html
The function signaturehandler(ctx,route, req, res) is a route hook that is used only in API gateway.
More info about Route hooks: https://moleculer.services/docs/0.13/moleculer-web.html#Route-hooks
Also, the req and res can't be passed to other services because these objects are not serializable.
Anyway, you might consider checking the video tutorial: https://www.youtube.com/watch?v=t4YR6MWrugw
It covers Moleculer's core concepts and shows how to call actions