Im just learn to use sequelize for my node.js project. For summary my project is ExpressJS+Typescript with Sequelize as ORM and Webpack as module bundler.
Below is my project structure.
src
-router
-server
--config
config.json
--controllers
index.ts
User.ts
--migrations
--models
index.js
user.js
--seeders
App.ts
index.ts
(sorry can not post picture yet, new user to stackoverflow)
I have build some simple router '/user' and expect it should call the user controller and call sequelize method findAll() from my models module, but the result is its error says Cannot read property 'list' of undefined. Below is my code:
models/index.js
const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(module.filename);
const env = process.env.NODE_ENV || 'development';
const config = require(`${__dirname}/../config/config.json`)[env];
const db = {};
let sequelize;
if (config.use_env_variable) {
sequelize = new Sequelize(process.env[config.use_env_variable]);
} else {
sequelize = new Sequelize(
config.database, config.username, config.password, config
);
}
fs
.readdirSync(__dirname)
.filter(file =>
(file.indexOf('.') !== 0) &&
(file !== basename) &&
(file.slice(-3) === '.js'))
.forEach(file => {
const model = sequelize.import(path.join(__dirname, file));
db[model.name] = model;
});
Object.keys(db).forEach(modelName => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
export default db;
models/user.js
export default function(sequelize, DataTypes) {
var user = sequelize.define('user', {
username: DataTypes.STRING,
name: DataTypes.STRING,
email: DataTypes.STRING,
password: DataTypes.STRING,
phone: DataTypes.STRING,
wallet: DataTypes.DECIMAL
}, {
classMethods: {
associate: function(models) {
// associations can be defined here
user.hasMany(models.top_up);
}
}
});
return user;
};
controllers/User.ts
let user = require('../models').user;
export default {
list(req, res) {
return user
.findAll()
.then(topUp => res.status(200).send(topUp))
.catch(error => res.status(400).send(error));
}
};
controllers/Index.ts
import users from './User'
export default {
users
}
router/router.ts
import * as express from 'express';
const userController = require('../server/controllers').users;
// Init express router
let router = express.Router();
// Setting API URL
router.get('/', (req, res, next) => {
res.json({
message: 'Hello World!'
});
});
router.get('/about',(req, res, next) => {
res.send('<p>This is about about</p>');
});
router.get('/user', userController.list());
export default router
Fyi, all of my project configuration for start express server, typescript compile and webpack bundle is fine already, and the other route for '/' and '/about' is work fine. I know there is something I'm missing, im still new to sequelize, thanks for help.
TL;DR: The server/controllers/index.ts does not export a binding named users, itexports a binding named default.
You are importing the controllers/Index.ts module using the require function. In a CommonJS environment, this imports the entire module.exports object. As currently transpiled by TypeScript, every named export of the required module is exposed as a property of the import.
An export default clause implies an export named default. As per the ES Module specification, there is a shorthand for importing the default export of a module. That shorthand is
import userController from '../server/controllers';
On the other hand, the syntax
import userController = require('../server/controllers');
or
const userController = require('../server/controllers'); // (or let or var)
imports an object with a property corresponding to each export. In this case it has the shape
{ default }
So if you use require, you need to write
import userController = require('../server/controllers').default;
or
import userController from '../server/controllers';
All ES Module style exports are named, including the default which is named default.
To illustrate this, consider the following, more verbose but semantically identical form
import {default as userController} from '../server/controllers';
If you would prefer to stick with CommonJS style exports, eschewing ES Modules when working in NodeJS, the idiomatic way to export a single object as the entire module (the object returned by require)
You may write
// ../server/controllers/index.ts
export = {
list(req, res) {
return user
.findAll()
.then(topUp => res.status(200).send(topUp))
.catch(error => res.status(400).send(error));
}
};
Personally, I would stick with what you have and write
import userController from '../server/controllers';
Related
I'm want to unit test Next.js API route with Prisma in JavaScript. Unfortunately, the Prisma's unit testing guide is written for Typescript.
I have jest.config.js which will setup the mock in jest.setup.js
const nextJest = require("next/jest");
const createJestConfig = nextJest({
dir: "./",
});
const config = {
setupFilesAfterEnv: ["<rootDir>/jest.setup.js"],
moduleDirectories: ["node_modules", "<rootDir>/"],
moduleNameMapper: {
"#/(.*)$": "<rootDir>/$1",
},
};
module.exports = createJestConfig(config);
and this is how I configured the mock in jest.setup.js
import prisma from "#/utils/client";
jest.mock("#/utils/client", () => ({
__esModule: true,
default: {
user: {
findMany: jest.fn(),
},
// ... and each and everyone of the entities
},
}));
export const prismaMock = prisma;
and the following test case passed
describe("Calculator", () => {
it("renders a calculator", async () => {
await prismaMock.user.findMany.mockResolvedValue(["abc]);
const { req, res } = createMocks();
await handler(req, res);
expect(res._getStatusCode()).toBe(200);
expect(res._getData()).toBe('["abc"]');
});
});
With this approach, I have to mock each and everyone of the models and function in jest.setup.js. Is there a way to mock all the models and functions automatically? Is there a similar JavaScript library which provides mockDeep from jest-mock-extended?
I'm using fastify-cli for building my server application.
For testing I want to generate some test JWTs. Therefore I want to use the sign method of the fastify-jwt plugin.
If I run the application with fastify start -l info ./src/app.js everything works as expected and I can access the decorators.
But in the testing setup I get an error that the jwt decorator is undefined. It seems that the decorators are not exposed and I just can't find any error. For the tests I use node-tap with this command: tap \"test/**/*.test.js\" --reporter=list
app.js
import { dirname, join } from 'path'
import autoload from '#fastify/autoload'
import { fileURLToPath } from 'url'
import jwt from '#fastify/jwt'
export const options = {
ignoreTrailingSlash: true,
logger: true
}
export default async (fastify, opts) => {
await fastify.register(jwt, {
secret: process.env.JWT_SECRET
})
// autoload plugins and routes
await fastify.register(autoload, {
dir: join(dirname(fileURLToPath(import.meta.url)), 'plugins'),
options: Object.assign({}, opts),
forceESM: true,
})
await fastify.register(autoload, {
dir: join(dirname(fileURLToPath(import.meta.url)), 'routes'),
options: Object.assign({}, opts),
forceESM: true
})
}
helper.js
import { fileURLToPath } from 'url'
import helper from 'fastify-cli/helper.js'
import path from 'path'
// config for testing
export const config = () => {
return {}
}
export const build = async (t) => {
const argv = [
path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'src', 'app.js')
]
const app = await helper.build(argv, config())
t.teardown(app.close.bind(app))
return app
}
root.test.js
import { auth, build } from '../helper.js'
import { test } from 'tap'
test('requests the "/" route', async t => {
t.plan(1)
const app = await build(t)
const token = app.jwt.sign({ ... }) //-> jwt is undefined
const res = await app.inject({
method: 'GET',
url: '/'
})
t.equal(res.statusCode, 200, 'returns a status code of 200')
})
The issue is that your application diagram looks like this:
and when you write const app = await build(t) the app variable points to Root Context, but Your app.js contains the jwt decorator.
To solve it, you need just to wrap you app.js file with the fastify-plugin because it breaks the encapsulation:
import fp from 'fastify-plugin'
export default fp(async (fastify, opts) => { ... })
Note: you can visualize this structure by using fastify-overview (and the fastify-overview-ui plugin together:
I am coding a simple NodeJS Express REST API, using TypeScript. I have some environment variables that I load with dotenv.
I access my .env variables at two different stages in my code: index.ts, which is my start file, and in a MyControllerClass.ts file. To access these variables, the code is process.env.MY_ENV_VAR. To load them for the application, the code is dotenv.config().
As my index.ts file seems to be the root of my program (I configure my app in it), I use dotenv.config() to load my .env file for the rest of the program. However, in my MyControllerClass.ts file, in the constructor, if I do console.log(process.env.MY_ENV_VAR), I get "undefined". I could workaround this by adding a dotenv.config() in my constructor (it works) but it's nonsense to me to have it here.
How do I use dotenv.config() once and for all in my program, in a readable manner (like in an appropriate .ts file)? and more generally: what is a NodeJS Express loading cycle?
Here is a sample of the file structure of my code
src
├── index.ts
├── Authentication
│ └── authentication.router.ts
│ └── authentication.controller.ts
Here is the code of index.js
/**
* Required External Modules
*/
import * as dotenv from "dotenv";
import express from "express";
import cors from "cors";
import helmet from "helmet";
import { authenticationRouter } from "./authentication/authentication.router"
dotenv.config();
/**
* App Variables
*/
if(!process.env.PORT) {
process.exit(1);
}
const PORT: number = parseInt(process.env.PORT as string, 10);
const app = express();
/**
* App Configuration
*/
app.use(helmet());
app.use(cors());
app.use(express.json());
app.use(authenticationRouter);
app.use("api/authenticate/", authenticationRouter);
/**
* Server Activation
*/
app.listen(PORT, () => {
console.log(`Listening on port ${PORT}`);
});
Here is the code of authentication.router.ts
import express, { Request, Response } from "express";
import { AuthenticatorController } from "./authentication.controller";
export const authenticationRouter = express.Router();
const authenticatorController = AuthenticatorController.getInstance();
authenticationRouter.post("/api/authenticate", async (req: Request, res: Response) => {
try {
if (await authenticatorController.authenticate(req.body.login, req.body.password)) {
res.send({"status": "ok"})
} else
res.send({"status": "Error"})
} catch (e) {
console.debug(e)
res.send({"status": "500"});
}
});
Here is the code of authentication.controller.ts
import { ClientSecretCredential } from "#azure/identity";
import { SecretClient } from "#azure/keyvault-secrets";
import { Authenticator } from "./api/Authenticator";
import * as dotenv from "dotenv";
dotenv.config();
export class AuthenticatorController implements Authenticator {
private static singleInstance: AuthenticatorController | null = null;
private azureSecretCredential= new ClientSecretCredential(
process.env.AZURE_TENANT_ID as string,
process.env.AZURE_CLIENT_ID as string,
process.env.AZURE_CLIENT_SECRET as string);
private azureSecretClient = new SecretClient(
process.env.KEY_VAULT_URL as string,
this.azureSecretCredential);
private constructor () {}
public static getInstance(): AuthenticatorController {
if (this.singleInstance === null) {
this.singleInstance = new AuthenticatorController();
}
return this.singleInstance;
}
public async authenticate(login: string, password: string): Promise<Boolean> {
let isAuthenticated = false;
try {
const secret = await this.azureSecretClient.getSecret(login)
if (secret.name === login) {
if (secret.value === password) {
isAuthenticated = true;
}
}
} catch (e) {
console.debug(e);
}
return isAuthenticated;
}
}
You only call dotenv.config() once:
As early as possible in your application, require and configure
dotenv.
require('dotenv').config()
Therefore index.ts seems to be correct, process.env should then hold your parsed values. Maybe you can use something like this to make sure, data is parsed correctly:
const result = dotenv.config();
if (result.error) {
throw result.error;
}
console.log(result.parsed);
Edit:
You can try the following. I changed your exports a bit, because there is no need for a singleton within your controller.
authentication.router.ts:
// Imports (no dotenv; no dotenv.config())
// [...]
// Import controller
import { authenticatorController } from "./authentication.controller";
export const authenticationRouter = express.Router();
// Adding routes
// [...]
authentication.controller.ts:
// Imports (no dotenv; no dotenv.config())
// [...]
class AuthenticatorController implements Authenticator {
// [...]
}
export const authenticatorController = new AuthenticatorController();
index.ts:
// Imports (dotenv)
// [...]
const { error, parsed } = dotenv.config();
if (error) {
throw error;
}
console.log(parsed);
// [...]
app.use("api/authenticate/", authenticationRouter);
// [...]
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.
I'm starting out with vue and nuxt, I have a project using vuetify and I'm trying to modify the carousel component to dynamically load images from the static folder. So far I've come up with:
<template>
<v-carousel>
<v-carousel-item v-for="(item,i) in items" :key="i" :src="item.src"></v-carousel-item>
</v-carousel>
</template>
<script>
function getImagePaths() {
var glob = require("glob");
var options = {
cwd: "./static"
};
var fileNames = glob.sync("*", options);
var items = [];
fileNames.forEach(fileName =>
items.push({
'src': '/'+fileName
})
);
return items;
}
export default {
data() {
return {items :getImagePaths()};
}
};
</script>
When I test this I see:
ERROR in ./node_modules/fs.realpath/index.js
Module not found: Error: Can't resolve 'fs' in '....\node_modules\fs.realpath'
ERROR in ./node_modules/fs.realpath/old.js
Module not found: Error: Can't resolve 'fs' in ....\node_modules\fs.realpath'
ERROR in ./node_modules/glob/glob.js
Module not found: Error: Can't resolve 'fs' in '....\node_modules\glob'
ERROR in ./node_modules/glob/sync.js
Module not found: Error: Can't resolve 'fs' in '.....\node_modules\glob'
googling this I see a bunch of references like https://github.com/webpack-contrib/css-loader/issues/447.
These suggest that you have to midify the webpack config file with something like:
node: {
fs: 'empty'
}
I know very little about webpack. I found https://nuxtjs.org/faq/extend-webpack/ , but am not sure how to modify the webpack config file in this case.
How do I do this?
You can't use NodeJs specific module on browser.
To solve your issue, you can create an API using Nuxt server middleware. The code below, inspired by https://github.com/nuxt-community/express-template.
Create a file, index.js in api/index.js. Then fill it with:
const express = require('express')
// Create express instance
const app = express()
// Require API routes
const carousel = require('./routes/carousel')
// Import API Routes
app.use(carousel)
// Export the server middleware
module.exports = {
path: '/api',
handler: app
}
Create carousel.js in api/routes/carousel.js. Then fill it with:
const { Router } = require('express')
const glob = require('glob')
const router = Router()
router.get('/carousel/images', async function (req, res) {
const options = {
cwd: './static'
}
const filenames = glob.sync('*', options)
let items = [];
filenames.forEach(filename =>
items.push({
'src': '/'+filename
})
);
return res.json({ data: items })
})
module.exports = router
Register your server middleware in nuxt.config.js
module.exports = {
build: {
...
},
serverMiddleware: [
'~/api/index.js'
]
}
Call the api in your page / component. I assume you're using Axios here (axios-module).
<script>
export default {
async asyncData ({ $axios }) {
const images = (await $axios.$get('/api/carousel/images')).data
return { images }
}
}
</script>
I know this is an old question, but it may be helpful for someone to disable fs in their browser.
Like this:
nuxt.config.js
build: {
extend (config, { isDev, isClient }) {
config.node= {
fs: 'empty'
}
// ....
}
},
Add this in your nuxt-config.js:
build: { extend (config, { isDev, isClient }) {
config.node = {
fs: 'empty'
}
// ....
}},