Question
In a TypeScript file named app.spec.ts, I have this import:
import app from './app';
ESLint reports this error:
2:17 error Unable to resolve path to module './app' import/no-unresolved
The file ./app.ts does exist, but it has not been compiled to ./app.js yet. After compiling the TypeScript file to JavaScript, the error disappears.
Since ESLint is being used with TypeScript, I want it to resolve imports against .ts files rather than requiring .js output files.
My ESLint configuration already includes TypeScript parsing:
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
}
}
How can I configure ESLint so that module resolution works correctly with .ts files?
For context, app.ts contains:
import bodyParser from 'body-parser';
import express from 'express';
import graphqlHTTP from 'express-graphql';
import { buildSchema } from 'graphql';
const app = express();
const schema = buildSchema(`
type Query {
hello: String
}
`);
const root = { hello: () => 'Hello world!' };
app.use(bodyParser());
app.use('/graphql', graphqlHTTP({
schema,
rootValue: root,
graphiql: true,
}));
export default app;
Short Answer
By the end of this page, you will understand why ESLint can parse TypeScript but still fail to resolve TypeScript imports, and how to fix that by configuring the correct import resolver. You will also learn the difference between parsing, linting, and module resolution, plus common mistakes that cause import/no-unresolved errors in TypeScript projects.
Concept
ESLint and TypeScript solve different problems.
- TypeScript checks types and understands
.tsfiles. - ESLint checks code style and code-quality rules.
- The rule
import/no-unresolvedcomes from eslint-plugin-import, and it needs its own way to find imported files.
This is the key idea: setting the TypeScript parser does not automatically teach ESLint how to resolve TypeScript imports.
When you write:
import app from './app';
TypeScript knows that ./app may refer to ./app.ts. But the import/no-unresolved rule may still use Node-style resolution that mainly expects JavaScript files unless you configure a TypeScript-aware resolver.
That is why the error disappears after compilation: once app.js exists, the default resolver can find it.
To make ESLint work properly with TypeScript source files, you usually need:
@typescript-eslint/parserto parse TypeScript syntax.eslint-plugin-importfor import rules.eslint-import-resolver-typescriptso import rules can resolve and files.
Mental Model
Think of ESLint as a building inspector.
- The parser tells the inspector how to read the blueprint.
- The rules tell the inspector what problems to look for.
- The resolver tells the inspector where rooms and doors actually are.
In this case:
@typescript-eslint/parserhelps ESLint read TypeScript.import/no-unresolvedchecks whether an import path points to a real module.eslint-import-resolver-typescripthelps ESLint find.tsfiles.
If you only give ESLint the parser, it can read the file, but it may still not know where ./app points unless you also give it the right resolver.
Syntax and Examples
The usual fix is to install and configure the TypeScript import resolver.
Install the needed packages
npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-import eslint-import-resolver-typescript
ESLint configuration
{
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "import"],
"parserOptions": {
"project": "./tsconfig.json",
"sourceType": "module"
},
"settings": {
"import/resolver": {
"typescript": {
"project": "./tsconfig.json"
Step by Step Execution
Consider this file structure:
src/
app.ts
app.spec.ts
And this code in app.spec.ts:
import app from './app';
What happens without the TypeScript resolver
- ESLint opens
app.spec.ts. @typescript-eslint/parsersuccessfully parses the TypeScript syntax.- The rule
import/no-unresolvedchecks./app. - The default import resolver looks for files using its normal resolution rules.
- It may not treat
./app.tsas a valid target. - ESLint reports:
Unable to resolve path to module './app'
What happens after compiling
If app.js is generated:
- ESLint checks
./appagain. - The resolver now finds
./app.js. - The warning disappears.
What happens with
Real World Use Cases
This configuration is useful in many real projects:
Backend APIs
A Node.js API written in TypeScript often has imports like:
import server from './server';
import routes from './routes';
You want linting to work before build output exists.
React and frontend apps
React projects often import components without extensions:
import Button from './Button';
import useAuth from './useAuth';
These may point to .tsx or .ts files.
Test files
Test files commonly import source modules directly:
import app from './app';
Linting should work even when tests run against source files rather than compiled files.
Path aliases
Many TypeScript projects use aliases such as:
Real Codebase Usage
In real codebases, developers usually combine TypeScript import resolution with a few common patterns.
1. Lint source code, not build output
Teams usually lint .ts and .tsx files directly and ignore generated dist/ or build/ folders.
{
"ignorePatterns": ["dist", "build"]
}
2. Use extensionless imports consistently
Most TypeScript projects prefer:
import app from './app';
instead of:
import app from './app.ts';
This keeps imports cleaner and works better across build tools.
3. Align ESLint with tsconfig.json
If your TypeScript config defines base paths or aliases, the resolver should read the same so both tools agree.
Common Mistakes
Here are common reasons this error continues even after adding TypeScript support.
Mistake 1: Only setting the parser
Broken idea:
{
"parser": "@typescript-eslint/parser"
}
Why it fails:
- This only helps ESLint parse TypeScript syntax.
- It does not configure import resolution.
Fix:
- Install and configure
eslint-import-resolver-typescript.
Mistake 2: Forgetting eslint-plugin-import
Broken config:
{
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"]
}
Why it fails:
- The rule
import/no-unresolvedbelongs toeslint-plugin-import.
Fix:
Comparisons
| Concern | Tool/Setting | What it does | What it does not do |
|---|---|---|---|
| Parsing TypeScript syntax | @typescript-eslint/parser | Lets ESLint read .ts and .tsx files | Does not resolve imports |
| Import rules | eslint-plugin-import | Adds rules like import/no-unresolved | Does not automatically understand TS paths |
| TypeScript import resolution | eslint-import-resolver-typescript | Resolves .ts, .tsx, and TS path mappings | Does not replace the parser |
| Type checking |
Cheat Sheet
{
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "import"],
"parserOptions": {
"project": "./tsconfig.json",
"sourceType": "module"
},
"settings": {
"import/resolver": {
"typescript": {
"project": "./tsconfig.json"
}
}
},
"rules": {
"import/no-unresolved": "error"
}
FAQ
Why does ESLint fail even though TypeScript can find the file?
Because TypeScript and ESLint use different systems. TypeScript understands .ts files, but ESLint's import rule needs a resolver configured separately.
Is @typescript-eslint/parser enough to fix import/no-unresolved?
No. It only parses TypeScript syntax. You also need a TypeScript-aware import resolver.
Which package usually fixes this problem?
eslint-import-resolver-typescript is the usual fix when using eslint-plugin-import with TypeScript.
Should I import ./app.ts explicitly?
Usually no. In most TypeScript projects, extensionless imports like ./app are preferred.
Do I still need eslint-plugin-import if I use TypeScript?
If you want rules like import/no-unresolved, yes. That rule comes from eslint-plugin-import.
Can this also fix path aliases from tsconfig.json?
Yes, in many cases. The TypeScript resolver can read path mappings from your tsconfig.json.
Mini Project
Description
Create a tiny TypeScript project with two files that import each other through normal extensionless imports, then configure ESLint so import/no-unresolved works without compiling to JavaScript first. This demonstrates the difference between parsing TypeScript and resolving TypeScript modules.
Goal
Set up ESLint so a TypeScript import like import app from './app'; passes linting before any .js files are generated.
Requirements
- Create a TypeScript file that exports a default value.
- Create another TypeScript file that imports it using an extensionless relative path.
- Enable the
import/no-unresolvedrule. - Configure ESLint to resolve
.tsfiles correctly. - Verify the setup works without compiling the project first.
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.
Angular @ViewChild Returning Undefined: Lifecycle, Child Components, and Fixes
Learn why Angular @ViewChild can be undefined, when it becomes available, and how to access child components correctly using lifecycle hooks.