setting shared schema for multiple fastify routes - fastify

I have the following route
export default async function (fastify) {
// fastify routes here...
fastify.get(
'/',
{
schema: {
params: {
type: 'object',
properties: {
id: {
type: 'number',
description: 'configuration id',
},
},
},
},
},
async (req) => {
console.log(req.params);
return {};
},
);
}
// Prefix for fastify autoload
export const autoPrefix = `/configuration/:id/jobs`;
How can I set the parameter schema for all of my routes in that function so I don't to duplicate my param schema:
{
params: {
type: 'object',
properties: {
id: {
type: 'number',
description: 'configuration id',
},
},
},
}
I know I can do:
const params = {
type: 'object',
properties: {
id: {
type: 'number',
description: 'configuration id',
},
},
};
fastify.get(
'/',
{
schema: {
params,
},
},
async (req) => {
console.log(req.params);
return {};
},
);
But asking if there is a way that I won't need to do that for each route
Edit: I've opened an issue with a proposal fastify/fastify#4316

You can use the onRoute hook
const fastify = require('fastify')({ logger: true })
const params = {
type: 'object',
properties: {
id: {
type: 'number',
description: 'configuration id'
}
}
}
fastify.addHook('onRoute', function hook (routeOptions) {
if (!routeOptions.schema) {
routeOptions.schema = {}
}
if (!routeOptions.schema.params && routeOptions.path.includes(':id')) {
routeOptions.schema.params = params
}
})
fastify.get('/:id', async (request, reply) => {
return { hello: 'world' }
})
fastify.register(async function plugin (instance, opts) {
instance.post('/foo', async (request, reply) => {
return request.body
})
}, { prefix: '/:id' })
fastify.ready()

Related

Cannot initialise class correctly in NodeJS

I really need some help! I am developing a simple app in NodeJS using express and the Onion architecture. I have an infrastructure, application, domain and interface layer.
The problem occurs when I want to initialize my domain object in the application layer and pass it to the Repository. I am passing to the domain object the req.body
{
minTransactionValue: '1000000000000000000',
maxTransactionValue: '40000000000000000000',
author: 'Konstantina'
}
directly, but what I get in the console is RuleDomain {}
The CreateRule.js
const serialize = require('serialize-javascript');
const RuleDomain = require('../../domain/Rule');
class CreateRule extends EventEmitter {
constructor({ rulesRepository }) {
super();
this.rulesRepository = rulesRepository;
}
execute(ruleData) {
const rule = new RuleDomain(ruleData);
console.log(rule);
this.rulesRepository
.add(rule)
.then((newRule) => {
this.emit('SUCCESS', newRule);
})
.catch((error) => {
if (error.message === 'ValidationError') {
return this.emit('VALIDATION_ERROR', error);
}
this.emit('ERROR', error);
});
}
}
module.exports = CreateRule;
The domain constructed using structure package
const { attributes } = require('structure');
const RuleDomain = attributes({
id: Number,
uuid: {
type: String,
required: true,
},
minTransactionValue: {
type: String,
required: true,
},
maxTransactionValue: {
type: String,
required: true,
},
from: { type: String },
to: { type: String },
minConfirmations: {
type: Number,
},
author: {
type: String,
required: true,
},
createdAt: {
type: Date,
required: true,
},
})(
class RuleDomain {
isLegal() {
return true;
}
}
);
module.exports = RuleDomain;
The controller function where I call CreateRule and pass the req.body
create(req, res) {
const rule = {
minTransactionValue: req.body.minTransactionValue,
maxTransactionValue: req.body.maxTransactionValue,
author: req.body.author,
minConfirmations: req.body.confirmations,
};
rulesRepository = new rulesRepo(RuleModel);
const createRule = new CreateRule({ rulesRepository });
createRule
.on('SUCCESS', (rule) => {
res.status(201).json(rule);
})
.on('VALIDATION_ERROR', (error) => {
res.status(400).json({
type: 'ValidationError',
details: error.details,
});
})
.on('ERROR', (error) => {
res.sendStatus(500);
});
createRule.execute(req.body);
},
Please tell me what is wrong with the req.body and why isnt the object initialized correctly

Cast to ObjectId failed for value at path for model error

This is my Profile Schema:
const mongoose = require('mongoose');
const ProfileSchema = new mongoose.Schema({
user: {
// Special field type because
// it will be associated to different user
type: mongoose.Schema.Types.ObjectId,
ref: 'user',
},
company: {
type: String,
},
website: {
type: String,
},
location: {
type: String,
},
status: {
type: String,
required: true,
},
skills: {
type: [String],
required: true,
},
bio: {
type: String,
},
githubusername: {
type: String,
},
experience: [
{
title: {
type: String,
required: true,
},
company: {
type: String,
required: true,
},
location: {
type: String,
},
from: {
type: Date,
required: true,
},
to: {
type: Date,
},
current: {
type: Boolean,
default: false,
},
description: {
type: String,
},
},
],
education: [
{
school: {
type: String,
required: true,
},
degree: {
type: String,
required: true,
},
fieldofstudy: {
type: String,
required: true,
},
from: {
type: Date,
required: true,
},
to: {
type: Date,
},
current: {
type: Boolean,
default: false,
},
description: {
type: String,
},
},
],
social: {
youtube: {
type: String,
},
twitter: {
type: String,
},
facebook: {
type: String,
},
linkedin: {
type: String,
},
instagram: {
type: String,
},
},
date: {
type: Date,
default: Date.now,
},
});
module.exports = Profile = mongoose.model('profile', ProfileSchema);
This is my view api. It doesn't work. it only return Cast to ObjectId failed for value { 'experience._id': '5edcb6933c0bb75b3c90a263' } at path _id for model profile
router.get('/experience/viewing/:viewexp_id', auth, async (req, res) => {
try {
const exp = await Profile.findById({
'experience._id': req.params.viewexp_id,
});
if (!exp) {
return res.status(404).json({ msg: 'Experience not found' });
}
res.json(exp);
} catch (err) {
console.error(err.message);
res.status(500).send(err.message);
}
});
How can I fix this? I tried looking at the stackoverflow of the same errors. still it doesn't seem to work.
and this is what I am trying to hit
The problem is that you have to convert your string _id to mongoose object id using this function mongoose.Types.ObjectId and my suggestion is to use findOne function instead of findById,
var mongoose = require('mongoose');
router.get('/experience/viewing/:viewexp_id', auth, async (req, res) => {
try {
let id = mongoose.Types.ObjectId(req.params.viewexp_id);
const exp = await Profile.findOne(
{ "experience._id": req.params.viewexp_id },
// This will show your sub record only and exclude parent _id
{ "experience.$": 1, "_id": 0 }
);
if (!exp) {
return res.status(404).json({ msg: 'Experience not found' });
}
res.json(exp);
} catch (err) {
console.error(err.message);
res.status(500).send(err.message);
}
});
var mongoose = require('mongoose');
router.get('/experience/viewing/:viewexp_id', auth, async (req, res) => {
try {
const exp = await Profile.findOne({
'experience._id': mongoose.Types.ObjectId(req.params.viewexp_id),
});
if (!exp) {
return res.status(404).json({ msg: 'Experience not found' });
}
res.json(exp);
} catch (err) {
console.error(err.message);
res.status(500).send(err.message);
}
});
You are saving object id . but your param id is string. convert it in ObjectId. Please check my solution.
router.post(
"/",
[
auth,
[
check("status", "status is required").not().isEmpty(),
check("skills", "skills is required").not().isEmpty(),
],
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const {
company,
website,
location,
bio,
status,
githubuername,
skills,
youtube,
facebook,
twitter,
instagram,
linkedin,
} = req.body;
const profileFileds = {};
profileFileds.user = req.user.id;
if (company) profileFileds.company = company;
if (website) profileFileds.website = website;
if (location) profileFileds.location = location;
if (bio) profileFileds.bio = bio;
if (status) profileFileds.status = status;
if (githubuername) profileFileds.githubuername = githubuername;
if (skills) {
profileFileds.skills = skills.split(",").map((skill) => skill.trim());
}
//Build profile object
profileFileds.social = {};
if (youtube) profileFileds.social.youtube = youtube;
if (twitter) profileFileds.social.twitter = twitter;
if (facebook) profileFileds.social.facebook = facebook;
if (linkedin) profileFileds.social.linkedin = linkedin;
if (instagram) profileFileds.social.instagram = instagram;
try {
let profile = await Profile.findOne({ user: req.user.id });
if (profile) {
//update
profile = await Profile.findOneAndUpdate(
{ user: req.user.id },
{ $set: profileFileds },
{ new: true }
);
return res.json(profile);
}
//Create profile
profile = new Profile(profileFileds);
await profile.save();
res.json(profile);
} catch (err) {
console.error(err.message);
res.status(500).send("server Error");
}
}
);

How to use query parameter in Graphiql tool

I am using zomato api to make an restaurant review app with React, node and Graphql. As per the docuemnt, I am passing query parameters, but in graphiql tool I am getting 'Not defined'.
Here is the error:
graphiql tool error
Here is the api documentation reference:
curl -X GET --header "Accept: application/json" --header "user-key: abc" "https://developers.zomato.com/api/v2.1/restaurant?res_id=2"
My query is how can we test the api using query parameters in graphiql tool? Also let me know how can we update the query parameter in api to catch user input and correspondingly show the result.
Below is the code for schema.js which defined graphql schema
const axios = require("axios");
const {
GraphQLObjectType,
GraphQLString,
GraphQLInt,
GraphQLBoolean,
GraphQLList,
GraphQLFloat,
GraphQLSchema
} = require("graphql");
const RestaurantType = new GraphQLObjectType({
name: "Restaurant",
fields: () => ({
res_id: {
type: GraphQLInt
},
name: {
type: GraphQLString
},
url: {
type: GraphQLString
},
photos_url: {
type: GraphQLString
},
menu_url: {
type: GraphQLString
},
rating: {
type: UserRatingType
},
location: {
type: LocationType
}
})
});
const UserRatingType = new GraphQLObjectType({
name: "UserRating",
fields: () => ({
aggregate_rating: {
type: GraphQLFloat
}
})
});
const LocationType = new GraphQLObjectType({
name: "Location",
fields: () => ({
address: {
type: GraphQLString
},
locality: {
type: GraphQLString
},
zipcode: {
type: GraphQLInt
}
})
});
const userKey = 'abc';
const RootQuery = new GraphQLObjectType({
name: "RootQueryType",
fields: {
restaurants: {
type: new GraphQLList(RestaurantType),
resolve(parent, args) {
return axios
.get(`https://developers.zomato.com/api/v2.1/restaurant`, {
headers: {
'user-key': {userKey}
}})
.then(res => res.data)
.catch((error) => {
console.log('error is ' + error);
});
}
},
restaurant: {
type: RestaurantType,
args: {
res_id: {
type: GraphQLInt
}
},
resolve(parent, args) {
return axios
.get(`https://developers.zomato.com/api/v2.1/restaurant?
res_id`, {
params: {
res_id
},
headers: {
'user-key': {
userKey
}
}
})
.then(res => {
console.log(res.data)})
.catch((error) => {
console.log('error is ' + error);
});
}
}
}
});
module.exports = new GraphQLSchema({
query: RootQuery
});

How to refactor GraphQL schema using "graphql-compose-elasticsearch" to tranform GraphQL query into Elasticsearch query?

I was hosting my data using json server on localhost:3000(REST api) and using GraphQL to fetch data, and now I'd like to move data into a Elastic Search server, and I still want to use GraphQL as API gateway. I tried this graphql-compose-elasticsearch library to use GraphQL as a proxy for ElasticSearch.
https://github.com/graphql-compose/graphql-compose-elasticsearch
In my original GraphQL schema, I defined types, root query, resolvers. The code is like this:
const graphql = require('graphql');
const axios = require('axios');
const {
GraphQLObjectType,
GraphQLList,
GraphQLID,
GraphQLInt,
GraphQLString,
GraphQLBoolean,
GraphQLSchema,
GraphQLNonNull
} = graphql;
const RoomType = new GraphQLObjectType({
name: 'RoomType',
fields: () => ({
id: { type: GraphQLID },
roomName: { type: GraphQLString },
roomNumber: { type: GraphQLString },
floorId: { type: GraphQLInt },
hasImages: { type: GraphQLBoolean },
hasGLTF: { type: GraphQLBoolean },
hasPhotogrammetry: { type: GraphQLBoolean },
hasPointClouds: { type: GraphQLBoolean },
roomDescription: { type: GraphQLString },
floor: {
type: FloorType,
resolve(parentValue, args) {
return axios
.get(`http://localhost:3000/floors/${parentValue.floorId}`)
.then((resp) => resp.data);
}
},
assets: {
type: new GraphQLList(AssetType),
resolve(parentValue, args) {
return axios
.get(`http://localhost:3000/rooms/${parentValue.id}/assets`)
.then((resp) => resp.data);
}
}
})
});
const FloorType = new GraphQLObjectType({
name: 'FloorType',
fields: () => ({
id: { type: GraphQLID },
floorName: { type: GraphQLString },
floorDescription: { type: GraphQLString },
rooms: {
type: new GraphQLList(RoomType),
resolve(parentValue, args) {
return axios
.get(`http://localhost:3000/floors/${parentValue.id}/rooms`)
.then((resp) => resp.data);
}
}
})
});
const AssetType = new GraphQLObjectType({
name: 'AssetType',
fields: () => ({
id: { type: GraphQLID },
category: { type: GraphQLString },
assetName: { type: GraphQLString },
assetNumber: { type: GraphQLString },
roomId: { type: GraphQLString },
location: { type: GraphQLString },
isHeritageAsset: { type: GraphQLBoolean },
hasImages: { type: GraphQLBoolean },
hasGLTF: { type: GraphQLBoolean },
hasPhotogrammetry: { type: GraphQLBoolean },
hasPointClouds: { type: GraphQLBoolean },
assetDescription: { type: GraphQLString },
room: {
type: RoomType,
resolve(parentValue, args) {
return axios
.get(`http://localhost:3000/rooms/${parentValue.roomId}`)
.then((resp) => resp.data);
}
}
})
});
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
getRoom: {
type: RoomType,
args: { id: { type: new GraphQLNonNull(GraphQLID) } },
resolve(parentValue, { id }) {
return axios.get(`http://localhost:3000/rooms/${id}`).then((resp) => resp.data);
}
},
getFloor: {
type: FloorType,
args: { id: { type: new GraphQLNonNull(GraphQLID) } },
resolve(parentValue, { id }) {
return axios.get(`http://localhost:3000/floors/${id}`).then((resp) => resp.data); //to make it compatible between axios and graphql, a workaround
}
},
getAsset: {
type: AssetType,
args: { id: { type: new GraphQLNonNull(GraphQLID) } },
resolve(parentValue, { id }) {
return axios.get(`http://localhost:3000/assets/${id}`).then((resp) => resp.data); //to make it compatible between axios and graphql, a workaround
}
},
getAllRooms: {
type: new GraphQLList(RoomType),
resolve() {
return axios.get(`http://localhost:3000/rooms`).then((resp) => resp.data);
}
},
getAllAssets: {
type: new GraphQLList(AssetType),
resolve() {
return axios.get(`http://localhost:3000/assets`).then((resp) => resp.data);
}
},
getAllFloors: {
type: new GraphQLList(FloorType),
resolve() {
return axios.get(`http://localhost:3000/floors`).then((resp) => resp.data);
}
}
}
});
//expose this to the rest of the application
module.exports = new GraphQLSchema({
query: RootQuery
});
For Elasticsearch, the version is 7.0. I'm running on localhost:9200, and I have 3 indices, rooms, floors, and assets, and each of the index have a mapping. I tried to code like this:
const graphql = require('graphql');
const graphql_compose_elasticsearch = require('graphql-compose-elasticsearch');
const { elasticApiFieldConfig, composeWithElastic } = graphql_compose_elasticsearch;
const { GraphQLSchema, GraphQLObjectType } = graphql;
const elasticsearch = require('elasticsearch');
// Mapping obtained from ElasticSearch server
const floor_mapping = {
properties: {
floorId: {
type: 'text',
fields: {
keyword: {
type: 'keyword',
ignore_above: 256
}
}
},
hasGLTF: {
type: 'boolean'
},
hasImages: {
type: 'boolean'
},
hasPhotogrammetry: {
type: 'boolean'
},
hasPointClouds: {
type: 'boolean'
},
roomDescription: {
type: 'text',
fields: {
keyword: {
type: 'keyword',
ignore_above: 256
}
}
},
roomName: {
type: 'text',
fields: {
keyword: {
type: 'keyword',
ignore_above: 256
}
}
},
roomNumber: {
type: 'text',
fields: {
keyword: {
type: 'keyword',
ignore_above: 256
}
}
}
}
};
const room_mapping = {
//similar
};
const asset_mapping = {
//similar
};
const Room = composeWithElastic({
graphqlTypeName: 'RoomType',
elasticIndex: 'rooms',
elasticType: '_doc',
elasticMapping: room_mapping,
elasticClient: new elasticsearch.Client({
host: 'http://localhost:9200',
apiVersion: '7.0'
})
});
const Floor = composeWithElastic({
graphqlTypeName: 'FloorType',
elasticIndex: 'floors',
elasticType: '_doc',
elasticMapping: floor_mapping,
elasticClient: new elasticsearch.Client({
host: 'http://localhost:9200',
apiVersion: '7.0'
})
});
const Asset = composeWithElastic({
graphqlTypeName: 'AssetType',
elasticIndex: 'assets',
elasticType: '_doc',
elasticMapping: asset_mapping,
elasticClient: new elasticsearch.Client({
host: 'http://localhost:9200',
apiVersion: '7.0'
})
});
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
roomSearch: Room.getResolver('search').getFieldConfig(),
roomSearchConnection: Room.getResolver('searchConnection').getFieldConfig(),
elastic70: elasticApiFieldConfig({
host: 'http://localhost:9200',
apiVersion: '7.0'
})
}
})
});
module.default = { schema };
server.js is like this:
const graphqlHTTP = require('express-graphql');
const { schema } = require('./schema/schema_es');
// const schema = require('./schema/schema');
var cors = require('cors');
const server = require('express')();
//allow cross-origin
server.use(
'/',
cors(),
graphqlHTTP({
schema: schema,
graphiql: true,
formatError: (error) => ({
message: error.message,
stack: error.stack.split('\n')
})
})
);
const PORT = process.env.PORT || 6060;
server.listen(PORT, () => console.log(`Server started on port ${PORT}`));
I restarted the servers, and here is the error I got
invariant.esm.js:31 [GraphQL error]: Message: GraphQL middleware options must contain a schema., Location: undefined, Path: undefined
I have no idea how to how to transfrom graphql schema, rootquery..etc into ES query using this graphql-compose-elasticsearch, any help is appreciated!

Couldn't catch(construct) the right router

I read https://github.com/fastify/fastify/blob/master/docs/Routes.md
But my router doesn't seems to catch the right url with params
Url : /app/name?id=666&method=3&_=1553342444710
I tried:
fastify.get('/app/:id-:method:*', (request, reply) => {
fastify.get('/app/*', (request, reply) => {
fastify.get('/app/:id-:method:-:_', (request, reply) => {
Try this:
fastify.get('/app/name', {
schema: {
querystring: {
id: { type: 'integer' },
name: { type: 'string' },
_: { type: 'integer' },
}
},
},
(request, reply) => {
...

Resources