I am creating an authentication API with Typescript,typeorm,express and JWT
But upon trying to register (signup a new user), I get this error:
TypeError: bcrypt.hash is not a function
at User.<anonymous> (file:///home/e-wave/Desktop/junkies/typechat/public/entity/User.js:25:42)
at Generator.next (<anonymous>)
at file:///home/e-wave/Desktop/junkies/typechat/public/entity/User.js:16:71
at new Promise (<anonymous>)
at __awaiter (file:///home/e-wave/Desktop/junkies/typechat/public/entity/User.js:12:12)
at User.hashPassword (file:///home/e-wave/Desktop/junkies/typechat/public/entity/User.js:24:16)
at file:///home/e-wave/Desktop/junkies/typechat/public/controllers/control.js:81:23
at Generator.next (<anonymous>)
at fulfilled (file:///home/e-wave/Desktop/junkies/typechat/public/controllers/control.js:4:58)
The same thing happens when i try to sign a json web token payload, when i try to pare bcrypt hashes on logging in (i get a jwt.sign is not a function
and bcryptpare is not a function
respectively
here's the code in my index.ts file:
import "reflect-metadata";
import {createConnection} from "typeorm";
import User from "./entity/User.js";
import express from 'express';
import { router } from "./routes/routes.js";
import morgan from 'morgan';
import {config} from "dotenv"
config(
)
createConnection({
type:"postgres",
host: process.env.RDS_HOSTNAME,
port: 5432,
username: process.env.RDS_USERNAME,
password: process.env.RDS_PASSWORD,
// database: "database-1",
entities: [
User
],
migrations:["migration/*.js"],
cli:{
migrationsDir:"migration"
},
synchronize: false,
logging: false
}).then(connection => {
// here you can start to work with your entities
const app = express()
const PORT:string|number = process.env.PORT||3000
app.use(express.json())
app.use(morgan('dev'))
app.use('/',router)
app.listen(PORT, ():void=> console.log(` Database connected and this app is running on port ${PORT}`))
}).catch(error => console.error(error));
My User.ts file:
import {Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, BaseEntity} from "typeorm";
import {isEmail} from "class-validator"
import * as bcrypt from "bcryptjs";
@Entity()
export default class User extends BaseEntity{
@PrimaryGeneratedColumn("uuid")
id: string;
@Column({type:"varchar",unique:true,nullable:false})
email: string;
@Column({nullable:false})
password: string;
@CreateDateColumn()
createdAt:Date
@UpdateDateColumn()
updatedAt:Date
//hash password method before saving to db
async hashPassword() {
this.password = await bcrypt.hash(this.password,10);
return this.password
}
}
My control.ts file:
import {
Request,
Response
} from 'express';
import * as jwt from 'jsonwebtoken';
import {
validate
} from "class-validator"
import User from '../entity/User.js'
import {
getManager,
getRepository
} from "typeorm"
import * as bcrypt from "bcryptjs"
//create an expiry date for our jwt token 1 day
const MAXAGE: number = 24 * 60 * 60 * 1000
class MainControllers {
//sign a json web token
static wele = (req: Request, res: Response) => {
res.send({
"text": "message"
})
}
//render login view
static loginGet = (req: Request, res: Response) => {
res.render('login')
}
//logining with credentials
static loginPost = async(req: Request, res: Response) => {
try {
//login user
let {
email,
password
} = req.body
if (!email || !password) return res.status(404).json({
message: "Not found !"
})
let userLoginRepository = getRepository(User)
//find the user attempting to login via the database
const findUser = await userLoginRepository.findOne({
where: {
email
}
})
if (!findUser) return res.json({
code: 404,
message: "user not found in our records"
})
//pare passwords
let auth = await bcryptpare(password, findUser.password);
if (!auth) return res.status(401).json({
message: "invalid password"
})
//else login the user and create the token valid for one day
const token: string = jwt.sign({
email
}, process.env.JWT_SECRET || "", {
expiresIn: process.env.JWT_EXPIRES
})
return res.status(200).json({
message: "login successfully",
token
})
} catch (err) {
console.error(err)
res.status(500).send({
message: err.message
})
}
}
//render signup view
static signUpGet = (req: Request, res: Response) => {
res.render('signup')
}
//signing up with credentials
static signUpPost = async(req: Request, res: Response) => {
try {
let {
email,
password
} = req.body
const newUser = new User()
newUser.email = email
newUser.password = password
//validate the input fields
let errors = await validate(newUser)
console.log("errors are:", errors)
if (errors.length > 0) return res.status(500).json({
message: errors
})
//generate the token
const token: string = jwt.sign({
email
}, process.env.JWT_SECRET || "", {
expiresIn: process.env.JWT_EXPIRES
})
res.cookie('jwtoken', token, {
maxAge: MAXAGE,
signed: true,
httpOnly: true
})
await newUser.hashPassword()
const dbRepository = getRepository(User)
await dbRepository.save(newUser)
return res.status(201).send({
message: "user created",
user: newUser,
token: token
})
} catch (err) {
console.error(err);
return res.status(500).send({
message: err.message
});
}
}
}
export default MainControllers;
My routes.ts file:
import {Router} from 'express';
import MainControllers from '../controllers/control.js';
//import * as cookieParser from "cookie-parser"
import cookieParser from 'cookie-parser';
export const router = Router()
router.use(cookieParser())
router.get('/wele',MainControllers.wele);
router.get('/login',MainControllers.loginGet)
router.post('/login',MainControllers.loginPost);
router.get('/signup',MainControllers.signUpGet)
router.post('/signup',MainControllers.signUpPost)
Please what could I possibly be doing wrong?
EDIT
Here's my tsconfig.ts file:
{
"pilerOptions": {
/* Visit .json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental pilation */
"target": "ES2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
"module": "es2015", /* Specify module code generation: 'none', 'monjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
"lib": ["ES5","ES2015","ES2016","ES2017","ES2018"], /* Specify library files to be included in the pilation. */
// "allowJs": true, /* Allow javascript files to be piled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
"sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./public", /* Redirect output structure to the directory. */
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "posite": true, /* Enable project pilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental pilation information */
// "removeComments": true, /* Do not emit ments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
"strictPropertyInitialization": false, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
/* Module Resolution Options */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
"baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose bined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in pilation. */
"allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
"emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
},
"include": ["src"]
}
I am creating an authentication API with Typescript,typeorm,express and JWT
But upon trying to register (signup a new user), I get this error:
TypeError: bcrypt.hash is not a function
at User.<anonymous> (file:///home/e-wave/Desktop/junkies/typechat/public/entity/User.js:25:42)
at Generator.next (<anonymous>)
at file:///home/e-wave/Desktop/junkies/typechat/public/entity/User.js:16:71
at new Promise (<anonymous>)
at __awaiter (file:///home/e-wave/Desktop/junkies/typechat/public/entity/User.js:12:12)
at User.hashPassword (file:///home/e-wave/Desktop/junkies/typechat/public/entity/User.js:24:16)
at file:///home/e-wave/Desktop/junkies/typechat/public/controllers/control.js:81:23
at Generator.next (<anonymous>)
at fulfilled (file:///home/e-wave/Desktop/junkies/typechat/public/controllers/control.js:4:58)
The same thing happens when i try to sign a json web token payload, when i try to pare bcrypt hashes on logging in (i get a jwt.sign is not a function
and bcrypt.pare is not a function
respectively
here's the code in my index.ts file:
import "reflect-metadata";
import {createConnection} from "typeorm";
import User from "./entity/User.js";
import express from 'express';
import { router } from "./routes/routes.js";
import morgan from 'morgan';
import {config} from "dotenv"
config(
)
createConnection({
type:"postgres",
host: process.env.RDS_HOSTNAME,
port: 5432,
username: process.env.RDS_USERNAME,
password: process.env.RDS_PASSWORD,
// database: "database-1",
entities: [
User
],
migrations:["migration/*.js"],
cli:{
migrationsDir:"migration"
},
synchronize: false,
logging: false
}).then(connection => {
// here you can start to work with your entities
const app = express()
const PORT:string|number = process.env.PORT||3000
app.use(express.json())
app.use(morgan('dev'))
app.use('/',router)
app.listen(PORT, ():void=> console.log(` Database connected and this app is running on port ${PORT}`))
}).catch(error => console.error(error));
My User.ts file:
import {Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, BaseEntity} from "typeorm";
import {isEmail} from "class-validator"
import * as bcrypt from "bcryptjs";
@Entity()
export default class User extends BaseEntity{
@PrimaryGeneratedColumn("uuid")
id: string;
@Column({type:"varchar",unique:true,nullable:false})
email: string;
@Column({nullable:false})
password: string;
@CreateDateColumn()
createdAt:Date
@UpdateDateColumn()
updatedAt:Date
//hash password method before saving to db
async hashPassword() {
this.password = await bcrypt.hash(this.password,10);
return this.password
}
}
My control.ts file:
import {
Request,
Response
} from 'express';
import * as jwt from 'jsonwebtoken';
import {
validate
} from "class-validator"
import User from '../entity/User.js'
import {
getManager,
getRepository
} from "typeorm"
import * as bcrypt from "bcryptjs"
//create an expiry date for our jwt token 1 day
const MAXAGE: number = 24 * 60 * 60 * 1000
class MainControllers {
//sign a json web token
static wele = (req: Request, res: Response) => {
res.send({
"text": "message"
})
}
//render login view
static loginGet = (req: Request, res: Response) => {
res.render('login')
}
//logining with credentials
static loginPost = async(req: Request, res: Response) => {
try {
//login user
let {
email,
password
} = req.body
if (!email || !password) return res.status(404).json({
message: "Not found !"
})
let userLoginRepository = getRepository(User)
//find the user attempting to login via the database
const findUser = await userLoginRepository.findOne({
where: {
email
}
})
if (!findUser) return res.json({
code: 404,
message: "user not found in our records"
})
//pare passwords
let auth = await bcrypt.pare(password, findUser.password);
if (!auth) return res.status(401).json({
message: "invalid password"
})
//else login the user and create the token valid for one day
const token: string = jwt.sign({
email
}, process.env.JWT_SECRET || "", {
expiresIn: process.env.JWT_EXPIRES
})
return res.status(200).json({
message: "login successfully",
token
})
} catch (err) {
console.error(err)
res.status(500).send({
message: err.message
})
}
}
//render signup view
static signUpGet = (req: Request, res: Response) => {
res.render('signup')
}
//signing up with credentials
static signUpPost = async(req: Request, res: Response) => {
try {
let {
email,
password
} = req.body
const newUser = new User()
newUser.email = email
newUser.password = password
//validate the input fields
let errors = await validate(newUser)
console.log("errors are:", errors)
if (errors.length > 0) return res.status(500).json({
message: errors
})
//generate the token
const token: string = jwt.sign({
email
}, process.env.JWT_SECRET || "", {
expiresIn: process.env.JWT_EXPIRES
})
res.cookie('jwtoken', token, {
maxAge: MAXAGE,
signed: true,
httpOnly: true
})
await newUser.hashPassword()
const dbRepository = getRepository(User)
await dbRepository.save(newUser)
return res.status(201).send({
message: "user created",
user: newUser,
token: token
})
} catch (err) {
console.error(err);
return res.status(500).send({
message: err.message
});
}
}
}
export default MainControllers;
My routes.ts file:
import {Router} from 'express';
import MainControllers from '../controllers/control.js';
//import * as cookieParser from "cookie-parser"
import cookieParser from 'cookie-parser';
export const router = Router()
router.use(cookieParser())
router.get('/wele',MainControllers.wele);
router.get('/login',MainControllers.loginGet)
router.post('/login',MainControllers.loginPost);
router.get('/signup',MainControllers.signUpGet)
router.post('/signup',MainControllers.signUpPost)
Please what could I possibly be doing wrong?
EDIT
Here's my tsconfig.ts file:
{
"pilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental pilation */
"target": "ES2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
"module": "es2015", /* Specify module code generation: 'none', 'monjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
"lib": ["ES5","ES2015","ES2016","ES2017","ES2018"], /* Specify library files to be included in the pilation. */
// "allowJs": true, /* Allow javascript files to be piled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
"sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./public", /* Redirect output structure to the directory. */
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "posite": true, /* Enable project pilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental pilation information */
// "removeComments": true, /* Do not emit ments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
"strictPropertyInitialization": false, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
/* Module Resolution Options */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
"baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose bined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in pilation. */
"allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
"emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
},
"include": ["src"]
}
Here's is also my package.json file(in case there was any package i should have installed):
{
"name": "typechat",
"version": "1.0.0",
"description": "A chat app built with typescript and mongodb",
"main": "index.js",
"author": "E-wave",
"license": "MIT",
"type": "module",
"dependencies": {
"aws-sdk": "^2.931.0",
"bcryptjs": "^2.4.3",
"class-validator": "^0.13.1",
"cookie-parser": "^1.4.5",
"dotenv": "^10.0.0",
"express": "^4.17.1",
"jsonwebtoken": "^8.5.1",
"pg": "^8.6.0",
"reflect-metadata": "^0.1.10",
"typeorm": "0.2.34"
},
"devDependencies": {
"@types/bcryptjs": "^2.4.2",
"@types/cookie-parser": "^1.4.2",
"@types/express": "^4.17.12",
"@types/jsonwebtoken": "^8.5.2",
"@types/morgan": "^1.9.2",
"@types/mysql": "^2.15.18",
"@types/node": "^8.0.29",
"morgan": "^1.10.0",
"ts-node": "3.3.0",
"typescript": "^4.3.4"
},
"scripts": {
"start": "src/index.ts",
"dev": "tsc && nodemon public/index.js"
}
}
Share
Improve this question
edited Jul 4, 2021 at 17:35
E-WAVE
asked Jul 3, 2021 at 10:13
E-WAVEE-WAVE
1173 silver badges12 bronze badges
2
- Can you provide the contents of your tsconfig.json file? – aleksxor Commented Jul 3, 2021 at 20:17
- I have added it to the question @aleksxor – E-WAVE Commented Jul 4, 2021 at 17:36
2 Answers
Reset to default 4Most probably this is happening because of missing types or packages.
Some packages dont have typescript support out of the box, so there is a need to install respective types using the Definetly Typed repository.
So in your case, if you try to install the respective types for bcryptjs and jsonwebtoken, your issue might be solved.
$ npm i -D @types/bcryptjs @types/jsonwebtoken
As these are only needed on development, we can flag them with -D
to install the packages as dev depedencies only.
I would also suggest you to check out NestJS, a typescript framework for NodeJS with a lot of nice features and recipes.
The problem is you're using "module": "es2015"
setting in your tsconfig.json
file and "type": "module"
in package.json. But bcryptjs
is a monjs
module. As it does not support esm patibility options. The only export is has when imported from es2015 code is default
. Since you already have "allowSytheticDefaultImports": "true"
set you just have to rewrite your imports as:
...
import bcrypt from 'bcryptjs'
...
bcrypt.hash(...)
or as
...
import { default as bcrypt } from 'bcryptjs'
...
bcrypt.hash(...)
...