How to search or find data from multiple collection in mongoose using Node Js - node.js

I am building the MERN app of Movies, where I have to implement search field, but stuck at how I can run search or query through multiple collections in MongoDB and get the data based on search in NodeJS.
controllers/movies.js
import MoviePost from "../../models/movies/moviesSchema.js";
import mongoose from 'mongoose'
export const getAllMov=async(req,res)=>{
try {
const data=await MoviePost.find().sort({_id:-1})
res.status(201).json(data)
} catch (error) {
res.status(401).json('error')
}
}
export const getMovById=async(req,res)=>{
const {id} =req.params
try {
const data=await MoviePost.findById(id)
res.status(201).json(data)
} catch (error) {
res.status(401).json('error')
}
}
export const getMov=async(req,res)=>{
const limit=10
const page=1
try {
const data=await MoviePost.find().sort({_id:-1}).limit(limit * 1).skip((page-1)*limit)
res.status(201).json(data)
} catch (error) {
res.status(401).json('error')
}
}
export const createMov=async(req,res)=>{
const data=req.body
const movie=new MoviePost(data)
try {
await movie.save()
res.status(201).json(movie)
} catch (error) {
res.status(401).json('error')
}
}
export const updateMov=async(req,res)=>{
const {id}=req.params
const updateMovie=req.body
if(!mongoose.Types.ObjectId.isValid((id))) return res.status(401).json('no data with that id')
const updatedMovie=await MoviePost.findByIdAndUpdate(id,updateMovie,{new:true})
res.status(201).json(updatedMovie)
}
export const deleteMov=async(req,res)=>{
const {id}=req.params
if(!mongoose.Types.ObjectId.isValid((id))) return res.status(401).json('no data with that id')
await MoviePost.findByIdAndRemove(id)
res.status(201).json('data Deleted')
}
export const getDataBySearch=async(req,res)=>{
const {searchQuery}=req.query
try {
const title=new RegExp(searchQuery,'i')
const data=await MoviePost.find({title})
res.status(201).json(data)
} catch (error) {
res.status(404).json({message:error.message})
}
}
in models there are three mongoose model with same schema, models named as in codes:
Movies
Trending
Webshow
I am putting movies.js here same applies for all models
models/movies.js
import mongoose from 'mongoose'
const MovieSchema=mongoose.Schema({
poster:String,
youtube:String,
title:String,
genre:[String],
director:String,
duration:String,
quality:String,
release:String,
imdb:String,
name:String,
description:String,
detailtitle:String,
screenshots:[String],
createdAt:{
type:Date,
default:new Date()
}
})
var WebShowPost=mongoose.model('Webshow',MovieSchema)
export default WebShowPost
as for the models routes are all similar.
routes/movies.js
import express from 'express'
const router=express.Router()
import { createMov, deleteMov, getAllMov, getMov, getMovById, updateMov,getDataBySearch } from '../../controllers/movies/movies.js'
router.get('/search',getDataBySearch)
router.get('/',getMov)
router.get('/all',getAllMov)
router.get('/:id',getMovById)
router.post('/',createMov)
router.patch('/:id',updateMov)
router.delete('/:id',deleteMov)
export default router
in main server.js
import bodyParser from 'body-parser'
import cors from 'cors'
import dotenv from 'dotenv'
import mongoose from 'mongoose'
import express from 'express'
import movieRoutes from './routes/routes.js'
import AdminRoutes from './routes/Admin.js'
import trendingCardRouter from './routes/movies/Trending.js'
import movieCardRouter from './routes/movies/movies.js'
import WebShowRouter from './routes/movies/WebShow.js'
const app=express()
dotenv.config({path:'./.env'})
app.use(bodyParser.json({limit:'4gb',extended:true}))
app.use(bodyParser.urlencoded({limit:'4gb',extended:true}))
app.use(cors())
app.use('/movies',movieRoutes)
app.use('/admin',AdminRoutes)
app.use('/trendCard',trendingCardRouter)
app.use('/movieCard',movieCardRouter)
app.use('/webshowCard',WebShowRouter)
app.get('/',(req,res)=>{
res.send('hello')
})
mongoose.connect(process.env.MONGO_URI,{useNewUrlParser:true,useUnifiedTopology:true})
.then(()=>app.listen('5000',()=> console.log('connected')))
.catch((err)=>console.log(err))
mongoose.set('useFindAndModify',false)
and please also tell me how to send data back directly to the frontend

for searching query in database you should send query for each databases and I recommend to use Promise.all for making your search faster for example use want to search in two collection , users and orders do it
Promise.all([
Users.find(query),
Orders.find(query)
])
.then(results=>{
var users = results[0] //// first call in Promise.all
var orders = results[1] //// second call in Promise.all
})
.catch(errors=>{
console.log('errors' : errors)
})

You can use Aggregation pipeline, and in first stage you can merge documents from different collections with $unionWith and then in second stage you can do search on some field with $match.
Collection_1.aggregate([
{
"$unionWith": {
"coll": "collection_2"
}
},
{
"$match": {
"filed_name": {
"$regex": "search_query"
}
}
}
])

Related

How to get query result from postgraphile running as a library

I have postgraphile running as an express middleware. For example:
const pgMiddleware = postgraphile(pool, SCHEMA, postgraphileConfig);
app.use(pgMiddleware);
How to get or intercept the result of a query or mutation without having a separate client?
For example when I send the below query
query {
personById(id: 1){
firstname
}
}
I want to be able to get the data sent back inside the same express app. How can I do that?
I believe what you are asking for is to be able to execute GraphQL operations against a PostGraphile schema from other routes/middlewares in Express without needing to make additional http requests. This is called schema only usage and you will specifically want to use withPostGraphileContext to execute your request and process results:
import type { Express } from "express";
import type { Pool } from "pg";
import {
gql,
makeProcessSchemaPlugin,
postgraphile,
withPostGraphileContext,
} from "postgraphile";
import PgSimplifyInflectorPlugin from "#graphile-contrib/pg-simplify-inflector";
import type { GraphQLSchema } from "graphql";
import { graphql } from "graphql";
// Register your middlewares with express
const schemaOnlyUsageApp = (app: Express, pool: Pool) => {
let schema: GraphQLSchema;
// This plugin will execute a callback each time the PostGraphile
// GraphQl schema is rebuit.
const schemaProcessorPlugin = makeProcessSchemaPlugin((newSchema) => {
schema = newSchema;
return schema;
});
// Register the PostGraphile middleware as normal for requests on /graphql (and /graphiql)
app.use(
postgraphile(pool, "my_schema", {
simpleCollections: "omit",
dynamicJson: true,
legacyRelations: "omit",
setofFunctionsContainNulls: false,
appendPlugins: [PgSimplifyInflectorPlugin, schemaProcessorPlugin],
watchPg: true,
graphiql: true,
enhanceGraphiql: true,
showErrorStack: true,
allowExplain: true,
})
);
// custom route that will execute a predefined gql query directly against the schema
app.get("/posts", async (req, res) => {
// arbitrary gql query
const query = gql`
query posts {
posts {
edges {
node {
id
title
body
likeCount
createdAt
}
}
}
}
`;
const result = await withPostGraphileContext(
{
// Reuse your pool to avoid creating additional connections
pgPool: pool,
},
async (context) => {
// execute your query directly and get results without making
// an additional http request!
const queryResult = await graphql({
schema,
source: query.loc?.source || "",
contextValue: { ...context },
});
return queryResult;
}
);
res.send(result);
});
};
export default schemaOnlyUsageApp;

Mocha, Supertest and Mongo Memory server, cannot setup hooks properly

I have a Nodejs, Express server that uses Mongodb as the Database. I am trying to write some tests but I cannot get it configured properly. I need to create a mongoose connection for each block ( .test.ts file) once and then clean up the db for the other tests. Depending on how I approach this I get two different behaviours. But first my setup.
user.controller.test.ts
import { suite, test, expect } from "../utils/index";
import expressLoader from "#/loaders/express";
import { Logger } from "winston";
import express from "express";
import request from "supertest";
import { UserAccountModel } from "#/models/UserAccount";
import setup from "../setup";
setup();
import { app } from "#/server";
let loggerMock: Logger;
describe("POST /api/user/account/", function () {
it("it should have status code 200 and create an user account", async function () {
//GIVEN
const userCreateRequest = {
email: "test#gmail.com",
userId: "testUserId",
};
//WHEN
await request(app)
.post("/api/user/account/")
.send(userCreateRequest)
.expect(200);
//SHOULD
const cnt: number = await UserAccountModel.count();
expect(cnt).to.equal(1);
});
});
And my other test post.repository.test.ts
import { suite, test, expect } from "../../utils/index";
import { PostModel } from "#/models/Posts/Post";
import PostRepository from "#/repositories/posts.repository";
import { PostType } from "#/interfaces/Posts";
import { Logger } from "winston";
#suite
class PostRepositoryTests {
private loggerMock: Logger;
private SUT: PostRepository = new PostRepository({});
#test async "Should create two posts"() {
//GIVEN
const given = {
id: "jobId123",
};
//WHEN
await this.SUT.CreatePost(given);
//SHOULD
const cnt: number = await PostModel.count();
expect(cnt).to.equal(1);
}
}
And my setup
setup.ts
import { MongoMemoryServer } from "mongodb-memory-server";
import mongoose from "mongoose";
export = () => {
let mongoServer: MongoMemoryServer;
before(function () {
console.log("Before");
return MongoMemoryServer.create().then(function (mServer) {
mongoServer = mServer;
const mongoUri = mongoServer.getUri();
return mongoose.connect(mongoUri);
});
});
after(function () {
console.log("After");
return mongoose.disconnect().then(function () {
return mongoServer.stop(true);
});
});
};
With the above setup I get
1) "before all" hook in "{root}":
MongooseError: Can't call `openUri()` on an active connection with different connection strings. Make sure you aren't calling `mongoose.connect()` multiple times. See: https://mongoosejs.com/docs/connections.html#multiple_connections
But if I don't import the setup.ts and I rename it to setup.test.ts, then it works but the data isn't cleared after each run so I actually have 10 new users created instead of 1. Every time I run it, it works and doesn't clear the data after it's finished.
Also I have a big issue where the tests hang and don't seem to finish. I am guessing that is because of the async, await in the tests or because of the hooks hanging.
What I want to happen is:
Each test should have it's own setup, the mongo memory server should be clean every time.
The tests should use async and await and not hang.
Somehow export the setup from 1) as a utility function so that I can reuse it in my code

TypeORM and MongoDB and Repositories: Cannot read property 'prototype' of undefined

I'm trying implement TypeORM with MongoDB using repositories. However, when I try to make use of repositories to manage the database, using the same structure as in this repository, things go a bit sideways. I'm getting the following error:
UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'prototype' of undefined
I have tried the following code:
import { Request, Response } from 'express';
import { getMongoRepository } from "typeorm";
import Task from "../models/Task";
export default class TasksController {
async listAll(request: Request, response: Response): Promise<Response> {
const tasksRepository = getMongoRepository(Task);
try {
const tasks = await tasksRepository.find();
return response.status(200).json({ "items": tasks });
} catch (err) {
return response.status(400).json({
message: err.message,
});
}
}
}
I know the error refers to implementing the .find() method. I have even managed to fetch the data, using a suggestion from this post replacing:
const tasks = await tasksRepository.find();
with
const tasks = await tasksRepository.createCursor(tasksRepository.find()).toArray();
but I still get the above mentioned error.
Anyone understands what's going on?
I have also managed to save data directly to the database through the use of the following script:
server.ts
import express from 'express';
import { createConnection } from 'typeorm'
const app = express();
const port = 3333;
createConnection();
app.use(express.json());
app.post('/tasks', (async (request, response) => {
const { item } = request.body;
task.item = item;
const task = new Task();
(await connection).mongoManager.save(task);
return response.send(task);
}))
app.listen(port, () =>
console.log(`Server running on port ${port}`)
);
TypeORM is not support mongodb v4.
https://github.com/nestjs/nest/issues/7798
You can use 3.7.0 instead.
I submitted a pull requests to resolve this. https://github.com/typeorm/typeorm/pull/8412 if anyone is looking for a workaround in the meantime.

Trouble migrating from graphql-import to just graphql-tools with ApolloServer, directives cease to work

My plight began as a simple desire to expand my graphql schema from a single .graphql file to multiple files so i can better organize the schema and so it wouldn;t grow to one huge file out of control.
My original layout was very straight forward and i had a working schema in a schema.graphql file. I would be able to parse it into a string using importSchema('server/schema.graphql') from the graphql-import library, which is now deprecated https://github.com/ardatan/graphql-import
They mention that it has been merged into graphql-tools in the newest version and provide a migration tutorial here https://www.graphql-tools.com/docs/migration-from-import The tutorial seems very straight forward since their first example pretty much illustrate exactly what my code looks like (except i dont use es6 import but old fashoined require):
import { importSchema } from 'graphql-import';
import { makeExecutableSchema } from 'graphql-tools';
const typeDefs = importSchema(join(__dirname, 'schema.graphql'));
const resolvers = {
Query: {...}
};
const schema = makeExecutableSchema({ typeDefs, resolvers });
And then they say to modify it, simply make these changes
import { loadSchemaSync } from '#graphql-tools/load';
import { GraphQLFileLoader } from '#graphql-tools/graphql-file-loader';
import { addResolversToSchema } from '#graphql-tools/schema';
const schema = loadSchemaSync(join(__dirname, 'schema.graphql'), { loaders: [new GraphQLFileLoader()] });
const resolvers = { Query: {...} };
const schemaWithResolvers = addResolversToSchema({
schema,
resolvers,
});
I made those changes but the vital difference is that they no longer use makeExecutableSchema() in their example, which is pretty important for me since i need to include the directives. What do i do now with the schema? How do i declare the directives? their documentation for directives still uses makeExecutableSchema but i cant use it anymore since the new loadSchemaSync function returns an object instead of a string literal which i would need to pass to typeDefs in makeExecutableSchema
I am using apollo-server, so it seemed a possible workaround was to just declare the directives in the apollo-server constructor and just pass in this new schemaWithResolvers as a schema as such
const server = new ApolloServer({
schema, //this includes now the returned value of using addResolversToSchema()
schemaDirectives : {
auth:AuthDirective,
authRole: AuthRoleDirective
}
context : ({req}) => //dostuff,
});
This allows my server to run, and i can perform queries and mutations, however, my directives are no longer working, and i no longer have authentication on protected queries.
I would like a way to import my .graphql file and parse it into a string so i can use it inside typeDefs as i used to with importSchema() or a way to declase my directies without using makeExecutableSchema() so that they continue working again!
I have gone up and down the documentation and seen other libraries and so far i keep coming up short, any tips or guidance is greatly appreciated
makeExecutableSchema is still part of graphql-tools and you can continue to use it as shown here in the docs. The issue with the example shown in the docs is that it's not equivalent to what you were doing before. You should use loadTypedefsSync instead:
import { loadTypedefsSync } from '#graphql-tools/load';
import { GraphQLFileLoader } from '#graphql-tools/graphql-file-loader';
import { addResolversToSchema } from '#graphql-tools/schema';
const sources = loadTypedefsSync(join(__dirname, 'schema.graphql'), { loaders: [new GraphQLFileLoader()] });
const documentNodes = sources.map(source => source.document);
const resolvers = { Query: {...} };
const schema = makeExecutableSchema({ typeDefs, resolvers });
Alternatively, if you go the loadSchema route, you should be able to apply the directives to your schema after loading it:
import { SchemaDirectiveVisitor } from "#graphql-tools/utils";
import { loadSchemaSync } from '#graphql-tools/load';
import { GraphQLFileLoader } from '#graphql-tools/graphql-file-loader';
import { addResolversToSchema } from '#graphql-tools/schema';
const schema = loadSchemaSync(join(__dirname, 'schema.graphql'), { loaders: [new GraphQLFileLoader()] });
const resolvers = { Query: {...} };
const schemaWithResolvers = addResolversToSchema({
schema,
resolvers,
});
SchemaDirectiveVisitor.visitSchemaDirectives(schemaWithResolvers, schemaDirectives);
I tried this way but I couldn't solve the problem. A unique solution that managed to take the following approach:
const { ApolloServer, makeExecutableSchema, gql} = require('apollo-server-express')
const { loadTypedefsSync } = require('#graphql-tools/load')
const { GraphQLFileLoader } = require('#graphql-tools/graphql-file-loader')
const path = require('path')
const sources = loadTypedefsSync(
path.resolve(__dirname, '../schema/root.graphql'),
{ loaders: [new GraphQLFileLoader()] }
)
const typeDefs = sources.map(source => source.document)
const schema = makeExecutableSchema({
typeDefs: gql`${typeDefs[0]}`,
resolvers,
})
I had the same issue I loaded the Schema via .graphql and I want to add the graphql-constraint-directive. My Solution was to load the schema with loadSchemaSync and then to use the wrapSchema to use the transform functions, you need also to add the directives into one of your .graphql files:
import { addResolversToSchema, wrapSchema } from 'graphql-tools';
import { GraphQLSchema } from 'graphql';
import resolvers from './resolver';
schema = loadSchemaSync('./**/*.graphql', {
loaders: [new GraphQLFileLoader()],
});
const schemaWithResolver = addResolversToSchema({
schema,
resolvers
});
const { constraintDirective } = require('graphql-constraint-directive')
const schemaConstrain = wrapSchema({
schema: schemaWithResolver,
transforms: [constraintDirective()]
})
Documentation to Schema Wrapping

Unknown type "Upload" in Apollo Server 2.6

I want to upload a file through GraphQL, and followed this article.
Here's the my schema:
extend type Mutation {
bannerAdd(
title: String!
image: Upload
): ID
}
However when I run the app, this gives me this error:
Unknown type "Upload". Did you mean "Float"?
Followed above article, Apollo Server will automatically generate Upload scalar, but why this is happening?
Also define Upload scalar manually also not working:
scalar Upload
...
Gives me this error:
Error: There can be only one type named "Upload".
Seems nothing wrong with my code. Is there an anything that I missed? Using Node#10.14.2, Apollo Server#2.6.1, Apollo Server Express#2.6.1 and polka#0.5.2.
Any advice will very appreciate it.
Fix this problem with GraphQLUpload of Apollo Server for create a custom scalar called FileUpload.
Server setup with Apollo Server:
const {ApolloServer, gql, GraphQLUpload} = require('apollo-server');
const typeDefs = gql`
scalar FileUpload
type File {
filename: String!
mimetype: String!
encoding: String!
}
type Query {
uploads: [File]
}
type Mutation {
singleUpload(file: FileUpload!): File!
}
`;
const resolvers = {
FileUpload: GraphQLUpload,
Query: {
uploads: (parent, args) => {},
},
Mutation: {
singleUpload: async (_, {file}) => {
const {createReadStream, filename, mimetype, encoding} = await file;
const stream = createReadStream();
// Rest of your code: validate file, save in your DB and static storage
return {filename, mimetype, encoding};
},
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
server.listen().then(({url}) => {
console.log(`🚀 Server ready at ${url}`);
});
Client Setup with Apollo Client and React.js:
You need to install the apollo-upload-client package too.
import React from 'react';
import ReactDOM from 'react-dom';
import { ApolloClient, InMemoryCache, ApolloProvider, gql, useMutation } from '#apollo/client';
import { createUploadLink } from 'apollo-upload-client';
const httpLink = createUploadLink({
uri: 'http://localhost:4000'
});
const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache()
});
const UPLOAD_FILE = gql`
mutation uploadFile($file: FileUpload!) {
singleUpload(file: $file) {
filename
mimetype
encoding
}
}
`;
function FileInput() {
const [uploadFile] = useMutation(UPLOAD_FILE);
return (
<input
type="file"
required
onChange={({target: {validity, files: [file]}}) =>
validity.valid && uploadFile({variables: {file}})
}
/>
);
}
function App() {
return (
<ApolloProvider client={client}>
<div>
<FileInput/>
</div>
</ApolloProvider>
);
}
ReactDOM.render(
<React.StrictMode>
<App/>
</React.StrictMode>,
document.getElementById('root')
);
Here's the solution what I did, adding custom scalar named "FileUpload" and add GraphQLUpload as resolver like this:
import { GraphQLUpload } from 'graphql-upload';
export const resolvers = {
FileUpload: GraphQLUpload
};
It works great, but it could be not perfect solution. Hope apollo fix this soon.
P.S. To upload file from your browser, you also need to set upload link in Apollo Client properly. Here's my code:
import { ApolloLink, split } from 'apollo-link';
import { createHttpLink } from 'apollo-link-http';
import { createUploadLink } from 'apollo-upload-client';
// Create HTTP Link
const httpLink = createHttpLink({
uri: ...,
credentials: 'include'
});
// Create File Upload Link
const isFile = value =>
(typeof File !== 'undefined' && value instanceof File) || (typeof Blob !== 'undefined' && value instanceof Blob);
const isUpload = ({ variables }) => Object.values(variables).some(isFile);
const uploadLink = createUploadLink({
uri: ...
credentials: 'include'
});
const terminatingLink = (isUpload, uploadLink, httpLink);
const link = ApolloLink.from([<Some Other Link...>, <Another Other Link...>, terminatingLink]);
const apolloClient = new ApolloClient({
link,
...
});
This issue can be caused by passing an executable schema (schema option) when initializing your server instead of the newer API of passing typeDefs and resolvers separately.
Old:
const server = new ApolloServer({
schema: makeExecutableSchema({ typeDefs, resolvers })
})
New:
const server = new ApolloServer({
typeDefs,
resolvers,
})
Or as explained in the docs:
Note: When using typeDefs, Apollo Server adds scalar Upload to your schema, so any existing declaration of scalar Upload in the type definitions should be removed. If you create your schema with makeExecutableSchema and pass it to ApolloServer constructor using the schema param, make sure to include scalar Upload.

Resources